Skip to content

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.