Send signed outgoing webhooks

Fed by the records themselves, delivered only to public HTTPS addresses
checked at every attempt, retried, and switched off after repeated failures.
A submitter's details travel only to webhooks that opted into them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 09:42:39 +02:00
parent f93775e549
commit 9bdbf834f6
23 changed files with 1325 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ use App\Models\Talk;
use App\Models\Technology;
use App\Models\Testimonial;
use App\Models\User;
use App\Models\Webhook;
use App\Support\Maintenance;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -94,6 +95,7 @@ class AdminApiContractTest extends TestCase
'repositories' => ['path' => '/v1/repositories'],
'media' => ['path' => '/v1/media'],
'tokens' => ['path' => '/v1/tokens'],
'webhooks' => ['path' => '/v1/webhooks'],
];
}
@@ -118,6 +120,8 @@ class AdminApiContractTest extends TestCase
$media = Media::factory()->create();
$page->comment('Primera nota', $this->caller);
ApiToken::issue($this->caller, 'CI', ['pages.*'], now()->addDay());
$webhook = Webhook::factory()->create(['is_active' => false]);
$webhook->deliveries()->create(['uuid' => 'contract', 'event' => 'ping', 'payload' => []]);
// One request, one documented path — the URL a real caller hits and
// the path key that describes it, side by side rather than derived
@@ -159,6 +163,9 @@ class AdminApiContractTest extends TestCase
'medium' => ["/v1/media/{$media->id}", '/v1/media/{media}'],
'revisions' => ["/v1/projects/{$project->id}/revisions", '/v1/projects/{project}/revisions'],
'tokens' => ['/v1/tokens', '/v1/tokens'],
'webhooks' => ['/v1/webhooks', '/v1/webhooks'],
'webhook' => ["/v1/webhooks/{$webhook->id}", '/v1/webhooks/{webhook}'],
'deliveries' => ["/v1/webhooks/{$webhook->id}/deliveries", '/v1/webhooks/{webhook}/deliveries'],
'review thread' => ["/v1/pages/{$page->id}/reviews", '/v1/pages/{page}/reviews'],
'users' => ['/v1/users', '/v1/users'],
'user' => ["/v1/users/{$this->caller->id}", '/v1/users/{user}'],