400.
Signature scheme
Each request carries one header:tis the time of signing in Unix seconds (UTC).- Each webhook endpoint has two secrets,
firstSecretandsecondSecret.s1is computed with the first secret ands2with the second. - Each signature is the lowercase hexadecimal HMAC-SHA256 of the string
<raw body>.<t>: the exact request body bytes as received (before any JSON parsing or re-serialisation), a dot, then the value oft. The key is the secret’s UTF-8 bytes.
s1 or s2 matches the HMAC computed with either of the secrets you have stored. Compare in constant time.
Protect against replayed requests by rejecting a t that is too far from your clock. We recommend a tolerance of 5 minutes. Replays that you start from the delivery log are signed again with a fresh t, so they pass this check. Retries and replays keep the body’s event.id, so deduplicate on it after the signature check.
Both secrets are shown only once: when you create the endpoint (both) and when you rotate one of them (the new one). AllRide never shows them again, not in the dashboard and not in the delivery log, which records only the header value that was sent.
Verify with a library
AllRide does not publish an SDK or a verification library. Every mainstream language ships HMAC-SHA256 and a constant-time comparison in its standard library, so the verification is about 20 lines; the handlers below need nothing else. If your web framework parses JSON before your code runs, configure the webhook route to give you the raw body (for exampleexpress.raw({ type: "application/json" }) in Express, request.get_data() in Flask, receiveText() in Ktor): a body that was parsed and serialised again no longer matches the signature.
Verify manually
Each handler below reads the raw body, verifiess1 or s2 against the secrets in the environment variables WEBHOOK_FIRST_SECRET and WEBHOOK_SECOND_SECRET (during a rotation one of them may be unset), answers 204 on success and 400 on failure. Process the event after you have acknowledged it.
openssl on a file that holds the exact body:
s1 (and the same command with WEBHOOK_SECOND_SECRET must equal s2). Make sure the file has no trailing newline that was not in the request.
Test vector
Use these documentation values to test your implementation. They are not real secrets.
Raw body (1476 bytes, UTF-8, no trailing newline; the two
€ signs are multi-byte characters):
- accepts the request with both secrets configured;
- accepts it with only the second secret configured (the situation during a rotation of the first secret);
- rejects it when one byte of the body changes;
- rejects it when your clock is more than 5 minutes away from
t(set “now” to1793610900when you run the vector).
Secret rotation
The two secrets let you rotate without dropping a single webhook, because AllRide signs every request with both:1
Rotate the first secret
Call Rotate the first secret (or use the developer dashboard). The response shows the new
firstSecret once. From now on, s1 uses the new secret and s2 still uses the unchanged second secret, so your receiver keeps accepting requests through s2.2
Update your receiver
Store the new first secret and deploy. Your receiver now accepts
s1 again.3
Rotate the second secret
Call Rotate the second secret, store the new
secondSecret and deploy again.manage-webhooks (and a recent sign-in for dashboard users). If a rotation response gets lost, a retry with the same Idempotency-Key returns the endpoint with the secret set to null; rotate that slot again. Rotate immediately if you suspect a secret has leaked.