User-Agent value and no list of source addresses: the signature is the only proof that
a request comes from iSnap. This page gives the scheme, code in three forms, and a test vector to
check your code before you go live.
The scheme is Standard Webhooks. A Standard Webhooks library in any language verifies a request with
the secret string exactly as iSnap returned it.
The three headers
No other header takes part in the signature. The
X-iSnap-* headers are not signed.
How the signature is computed
1
The signed content
The string
<webhook-id>.<webhook-timestamp>.<body>: the two header values and the body, joined
by periods. <body> is the request body exactly as received. The body is compact JSON in UTF-8.2
The key
The base64 decoding of the secret without its
whsec_ prefix: 48 bytes. It is not the
hexadecimal decoding of those 64 characters, and it is not the secret string itself.3
The signature
HMAC-SHA256 of the signed content with that key, encoded in base64, after the prefix
v1,.- Use the raw body. Compute the signature over the bytes you received, before any JSON parsing. A body that was parsed and serialized again does not verify.
- Compare in constant time. Use the comparison function of your cryptography library, not
==. - Check the timestamp. Refuse a request whose
webhook-timestampis more than 300 seconds away from your clock, earlier or later. The timestamp is renewed on every attempt, so this tolerance never refuses a legitimate retry or replay. Keep the clock of your server synchronized. - Return a status outside
2xxwhen the verification fails, and never410: a410response turns the subscription off. iSnap retries the delivery: see Retries.
Verify with the SDK
verifyWebhook(rawBody, headers, secret, options) checks the signature and the timestamp of a
request, then returns the parsed event. It is synchronous. It throws WebhookSignatureError on any
failure: a missing header, a malformed timestamp, a timestamp outside the tolerance, a signature
that does not match, or a body that is not an event.
verifyWebhook does not deduplicate: deduplicate on event_id. It accepts an event type the SDK
does not know. See SDK webhooks for the type guards that narrow data.
receiver.ts
Verify with Node.js
The module below usesnode:crypto and nothing else. Save it as verify.mjs.
verify.mjs
receiver.mjs
Verify with Python
The module below uses the standard library and nothing else. It needs Python 3.9 or later. Save it asverify.py.
verify.py
receiver.py
PORT environment variable, 3000 by default, and
read the secret from ISNAP_WEBHOOK_SECRET.
During a secret rotation
A rotation takes effect immediately, and iSnap never signs with the previous secret after it: see Secret rotation. To rotate without refusing deliveries, give your receiver both secrets for the time of the change.verifyWebhook, verifyDelivery and
verify_delivery all accept a list of secrets in place of one secret.
Test your verification
Check your code against this vector before you point a subscription at it. Every value is fake.v1,29SNUfgyZggRY4lxdCHTW2jcxTEA11sbup1oHxq0F0c=, it decoded the 64
characters of the secret as hexadecimal. iSnap never sends that value: decode them as base64.
The timestamp of the vector is in the past. Each test below sets the clock of the verification, or
widens its tolerance, for the test only. Each one checks three things: the vector is accepted, the
signature of the hexadecimal mistake is refused, and a request older than the tolerance is refused.
test vector ok. A real probe has the shape of this body: see
webhook.test.