Cbox Webhook Signature
Cbox Webhook Signature
Prove that an inbound webhook really came from the provider it claims to, across every provider you receive from, with one API.
// receiving
Route::post('/webhooks/github', GithubController::class)
->middleware('webhook.signature:github');
// sending
Http::webhookSignature('outbound')->post($subscriber->url, $payload);
What this is
A verification library. It checks signatures, and it signs outbound messages with the same code so the two can never drift. That is the whole scope.
It ships no migrations, no models, no queued jobs, and no webhook_calls table.
Storing deliveries, dispatching work, and retrying failures are decisions your application
has probably already made, and a library that made them again would be something you have
to fit around rather than something you drop in.
What it handles that a hand-rolled verifier does not
Most providers compute an HMAC; Postal signs asymmetrically with a private key. What differs — and what breaks integrations — is everything around it:
| Provider | Signed bytes | Digest | Encoding |
|---|---|---|---|
| GitHub | raw body | SHA-256 | hex |
| Stripe | {timestamp}.{body} |
SHA-256 | hex |
| Slack | v0:{timestamp}:{body} |
SHA-256 | hex |
| Shopify | raw body | SHA-256 | base64 |
| Standard Webhooks | {id}.{timestamp}.{body} |
SHA-256 | base64 |
| Twilio | URL + sorted parameters | SHA-1 | base64 |
| Mailgun | {timestamp}{token} |
SHA-256 | hex |
| Postal | raw body (RSA, public key) | SHA-256 / SHA-1 | base64 |
| Cbox | {timestamp}.{body} |
SHA-256 | hex |
Plus the details underneath: Stripe sends several valid signatures at once during a
rotation, the Standard Webhooks secret is base64 behind a whsec_ prefix, Twilio's
parameter sort is byte-wise rather than numeric, Postal's two headers create a downgrade
path that has to be closed deliberately, and every one of these headers can arrive in a
different case than the documentation shows.
Beyond the wire formats
- Secret rotation as a first-class concern. You cannot rotate a webhook secret atomically, so an endpoint holds a set of live secrets and reports which one verified.
- Replay defence. Timestamp binding where the provider supports it, plus optional single-use enforcement against a shared store.
- Outbound signing at send time.
Http::webhookSignature()signs the request from inside the client, so the bytes that are signed are the bytes that are sent — the caller never holds an encoded body that can drift from what the client transmits. - A typed failure reason on every refusal, so "our secret is missing" is distinguishable from "someone sent a bad signature" in your logs and your alerts.
- Testing helpers that sign with the production code path, so a test never has to reimplement the signature or mock the verifier away.
Sections
- Getting started — install, configure an endpoint, test it.
- Core concepts — the architecture, schemes, rotation, replay.
- Cookbook — per-provider recipes and outbound signing.
- Extension points — new providers, custom replay stores.
- Configuration — every key, and what it does.
- Security — threat model, and exactly which schemes are verified against published vectors.
Start with the quickstart.