Verify a webhook signature
Anyone can find your webhook URL and POST a fake payload to it. Signature verification is how your server tells a genuine InsurGrid delivery apart from an imposter — before you trust a single field in the body.
New term — signature verification: recomputing a cryptographic fingerprint of an incoming request on your side, using a secret only you and the sender know, and checking it matches the one the sender attached. A match proves the request came from the sender and wasn't altered in transit.
This is the default auth scheme for every InsurGrid webhook (see Register your endpoint & choose an auth type if you've configured a different one — the rest of this page doesn't apply to those).
How it works
Every delivery carries an x-signature header: an HMAC-SHA256 of the raw request body, computed with your signing secret (from the API section — see Authentication & tokens), hex-encoded.
New term — HMAC-SHA256: a hashing algorithm that mixes a secret key into the fingerprint it produces. Recomputing the same hash of the same bytes with the same secret always yields the same output — so if your recomputed value matches the header, the request is genuine.
The check has three parts:
Get the exact raw bytes InsurGrid sent — not the parsed-and-re-serialized JSON.
Compute
HMAC-SHA256(raw_body, signing_secret), hex-encoded.Compare it to the
x-signatureheader, using a timing-safe comparison. If it doesn't match, reject the request with401.
⚠️ The raw-bytes footgun
The single most common first-time bug: computing the HMAC over JSON.stringify(parsedBody) (or Python's json.dumps(parsed)) instead of the bytes as received.
Parsing JSON and re-serializing it does not reliably reproduce the original bytes — key order, whitespace, and number formatting can all differ. Even a one-byte difference produces a completely different hash, so the check fails even though the request is perfectly genuine.
The fix: capture the raw body before your framework parses it, and hash those bytes — never the parsed object.
Verify it · server
Node (Express):
Python (Flask):
Notice both handlers read the raw body before parsing JSON, verify first, and only trust req.body / request.get_json() after the check passes.
Why timing-safe comparison
A plain expected === signature string comparison returns as soon as it finds the first mismatched character — which means it takes slightly longer to reject a guess that gets more characters right. An attacker who can measure that timing difference can, in theory, guess a valid signature one byte at a time. crypto.timingSafeEqual (Node) and hmac.compare_digest (Python) take the same amount of time regardless of where the mismatch is, closing that side channel. Always use them for this comparison instead of === or ==.
Reject on mismatch
If the signature doesn't match, return 401 and stop — don't process the body, don't log it as a near-miss with full contents, and don't fall back to trusting the request anyway. A mismatch means either the secret is wrong on one side (check you copied the current one) or the request isn't from InsurGrid.
Using a different scheme instead
If signature verification doesn't fit your setup, you can configure your endpoint to authenticate deliveries a different way — HMAC-SHA256 with your own secret, Basic auth, a bearer token, or a static API-key header. See Register your endpoint & choose an auth type for all five options and when each makes sense. A static credential is simpler to check but, unlike an HMAC, it doesn't prove the body wasn't altered in transit.
