Signature Verification
Verify incoming webhooks with HMAC-SHA256, HMAC-SHA1, or Standard Webhooks.
Signatures prove a webhook came from the expected sender and not an attacker. The provider hashes the request body with a shared secret; hookstream recomputes the hash and compares with constant-time equality. Mismatch means 401.
Hookstream ships preconfigured signature verification for 15+ providers — Stripe, GitHub, Shopify, Clerk, Svix, Linear, and more. Use a source template and the secret is the only field you need to set.
Supported algorithms
| Algorithm | verification_type | Used by |
|---|---|---|
| HMAC-SHA256 | hmac-sha256 | Stripe, GitHub, Shopify, Slack, most modern providers |
| HMAC-SHA1 | hmac-sha1 | Legacy providers (pre-2020 GitHub, some CI systems) |
| Standard Webhooks | standard-webhooks | Svix, Clerk, Resend, and other Standard Webhooks adopters |
HMAC-SHA256 is the default recommendation. SHA-1 is still supported for legacy integrations but should not be chosen for new work.
How each algorithm works
HMAC-SHA256
The sender computes HMAC-SHA256(secret, request_body) and puts the hex digest in a header (for example Stripe-Signature or X-Hub-Signature-256). Hookstream:
- Reads the signature header you configured on the source.
- Recomputes the HMAC over the raw request body.
- Compares using constant-time equality.
A common prefix like sha256= is stripped automatically before comparison.
HMAC-SHA1
Same flow, but the hash function is SHA-1. Configure with verification_type: "hmac-sha1".
Standard Webhooks
Standard Webhooks is an open spec built on HMAC-SHA256 with a fixed set of headers:
webhook-id— unique event idwebhook-timestamp— Unix timestampwebhook-signature—v1,<base64_signature>(multiple versions allowed, comma separated)
The signed string is webhook-id.webhook-timestamp.body and the secret is base64-encoded. Configure with verification_type: "standard-webhooks".
Configure verification
Get the signing secret from your provider
In the provider's dashboard, create or view the webhook endpoint and copy the signing secret. For Stripe this is whsec_...; for GitHub it's whatever you typed into the "Secret" field when creating the hook.
Create a source with the secret
Either use a template from GET https://hookstream.io/v1/sources/templates or post the fields directly:
bashcurl -X POST https://hookstream.io/v1/sources \ -H "X-API-Key: $HOOKSTREAM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Stripe Webhooks", "verification_type": "hmac-sha256", "verification_secret": "whsec_...", "verification_header": "stripe-signature" }'
Point your provider at the ingest URL
Copy the URL from the source detail page — it looks like https://hookstream.io/v1/ingest/src_<id> — and paste it into the provider's webhook settings.
Verify with a real event
Send a test event from the provider (or use the Signature Validator to simulate one). Successful requests land in your Events page with verification_status: "verified".
Signature verification runs on the raw request body. If something upstream (a proxy, a transform) modifies the body before hookstream sees it, every signature will fail. Hookstream handles this correctly because it reads the body exactly once.
Test and debug
Two free tools help when a signature keeps failing:
- Signature Validator — paste the secret, headers, and body to see step-by-step verification output.
- HMAC Calculator — compute an HMAC over arbitrary input to cross-check what your sender should be producing.
Events that fail verification are still stored (with verification_status: "failed") so you can inspect the headers and body in the dashboard and figure out what went wrong.