Quickstart
Quickstart
1. Install
composer require cboxdk/laravel-webhook-signature
The service provider is auto-discovered. Publish the config when you are ready to declare endpoints:
php artisan vendor:publish --tag=webhook-signature-config
2. Declare an endpoint
An endpoint binds a name to a scheme and its secrets. Application code refers to the name, so the secret never appears in a controller, a test, or a diff.
// config/webhook-signature.php
'endpoints' => [
'github' => [
'scheme' => 'github',
'secrets' => [env('GITHUB_WEBHOOK_SECRET')],
],
],
3. Protect the route
Route::post('/webhooks/github', GithubController::class)
->middleware('webhook.signature:github');
Register it early in the stack. Anything that reads and rewrites the request body before this point changes the bytes the signature covers, and every delivery will then fail for reasons that look nothing like the cause.
4. Use the proven payload
use Cbox\WebhookSignature\ValueObjects\VerifiedWebhook;
class GithubController
{
public function __invoke(Request $request)
{
$webhook = VerifiedWebhook::fromRequestOrFail($request);
ProcessGithubEvent::dispatch($webhook->json())
->onQueue('webhooks');
return response()->noContent();
}
}
Read the payload from the VerifiedWebhook, not from $request->all(). They contain the
same data today; the difference is that one of them is the data that was actually signed.
5. Test it
use Cbox\WebhookSignature\Testing\InteractsWithWebhookSignatures;
uses(InteractsWithWebhookSignatures::class);
it('accepts a genuine delivery', function () {
$this->fakeWebhookEndpoint('github', 'github', 'test-secret');
$this->postSignedWebhook('/webhooks/github', 'github', ['action' => 'opened'])
->assertOk();
});
it('refuses an unsigned one', function () {
$this->fakeWebhookEndpoint('github', 'github', 'test-secret');
$this->postUnsignedWebhook('/webhooks/github', ['action' => 'opened'])
->assertUnauthorized();
});
Write both. A suite that only posts valid signatures proves the happy path and nothing at all about the control.
6. Try it by hand
php artisan webhook:sign github --body='{"action":"opened"}' --curl
Prints a runnable curl command signed with the endpoint's real secret.
7. Send signed webhooks of your own
Declare an endpoint the same way, then:
Http::webhookSignature('outbound')->post($subscriber->url, $payload);
That is the whole API — no headers to attach and no encoded body to keep in a variable. The signature is applied by middleware inside the HTTP client, at the moment the request goes out, so what is signed is by construction what is sent. See sending signed webhooks.
Next
- Choose a scheme for your provider, or describe one in config if it is not bundled.
- Plan a secret rotation before you need one.
- Decide about replay protection — it is off by default, deliberately.