Skip to main content
Every request iSnap sends to your endpoint is signed with the signing secret of the subscription: deliveries, retries, replays and probes. Verify the signature before you trust the body. iSnap promises no 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,.
Four rules make a verification correct:
  • 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-timestamp is 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 2xx when the verification fails, and never 410: a 410 response turns the subscription off. iSnap retries the delivery: see Retries.
In a web framework, read the raw body before any JSON middleware parses it:

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
The SDK runs on a server: Node.js 20 or later, or Bun 1.2 or later.

Verify with Node.js

The module below uses node:crypto and nothing else. Save it as verify.mjs.
verify.mjs
A receiver that uses it:
receiver.mjs

Verify with Python

The module below uses the standard library and nothing else. It needs Python 3.9 or later. Save it as verify.py.
verify.py
A receiver that uses it:
receiver.py
The three receivers listen on the port of the 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.
The expected header:
If your code computes 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.
Each test prints test vector ok. A real probe has the shape of this body: see webhook.test.