Verifying Signatures
Verify the X-GetBlock-Signature header of every webhook delivery, with ready-to-use Go, JavaScript and Python recipes, a published test vector and the secret rotation procedure.
Every delivery carries an X-GetBlock-Signature header: an HMAC-SHA256 of the request body made with the webhook's signing secret (whsec_…). Verify it on every request — it proves the request came from GetBlock and that the body was not changed.
Signature scheme
X-GetBlock-Signature: t=<unix seconds>,v1=<hex>[,v1=<hex>]
v1 = hex( HMAC-SHA256( secret, "<t>" + "." + body ) )t is the time the attempt was signed. There is one v1 per valid secret: one normally, two during a secret rotation.
Verification steps
Use the raw body bytes exactly as received, before any JSON parsing. A framework that parses and re-serialises the body changes the bytes and breaks the signature.
Parse the header. Elements are comma-separated
key=value; spaces around an element are allowed. Expect exactly onet(decimal digits) and at least onev1(64 hex digits, either case). Ignore unknown keys such asv2=…— the scheme may gain new ones. A secondt, a missing or non-numerict, nov1, av1that is not 64 hex digits, or an element without=is a malformed header.Check the time first: reject the request when
|now − t|is more than 300 seconds, in either direction. A clock problem on your side then shows as expired, not as a forged body.Compute the MAC for every secret you accept and compare it with every
v1in constant time. Any match is success. During a rotation the header carries twov1values — do not rely on their order.Header names are case-insensitive: many frameworks hand this one over as
X-Getblock-Signature.
Every attempt, retries included, is signed afresh with a new t. A request repeated within the 300 seconds cannot be told apart from a legitimate repeat of an at-least-once delivery, so de-duplicate on eventId + phase — see Handling Chain Reorganizations.
Do not rely on IP addresses to recognise GetBlock: the delivery addresses are not published and may change without notice. The signature is the only reliable check of origin. See Delivery source addresses.
Recipes
Each recipe is a complete program that uses only the standard library. The verifySignature / verify_signature function is what you copy into your handler, or import from the saved file as in Getting Started. The main function reads one JSON object from stdin — header, the body as body (text) or body_b64 (base64), secrets, and optional now and tolerance in seconds — and prints ok, malformed, expired, mismatch or no_secrets.
Where the raw body is in common frameworks: Go io.ReadAll(r.Body); Express express.raw({ type: "application/json" }), then req.body is a Buffer; Flask request.get_data(); FastAPI await request.body(); Django request.body.
Go 1.21 or later. Save as verify.go:
Node.js 18 or later. Save as verify.mjs:
Python 3.9 or later. Save as verify.py:
Self-test with the test vector
Every GetBlock signer and every recipe above reproduces this vector byte for byte. The secrets are fake and are never used in production. body is the confirmed-phase log_event from Delivery Format, compact, stored as a JSON string so no editor can change its bytes. Save it as signature_v1.json:
Run each recipe against it (jq builds the stdin object); the expected output is at the end of each line:
Rotating the secret
The secret is shown once: in the answer that creates the webhook and in each rotation answer. Rotate it with Rotate secret in the dashboard or with POST /api/v1/webhooks/{id}/secret/rotate:
Request body
Effect
previous_expires_at
none, {} or {"expire_previous": false}
Routine: the old secret stays valid for 24 hours; meanwhile every delivery carries two v1 values, one per secret
The end of the overlap
{"expire_previous": true}
Emergency (the secret has leaked): the old secret stops at once. In the dashboard: Rotate and revoke old secret
null
The answer is 200 with the new secret: {"secret": "whsec_…", "version": 4, "previous_expires_at": "2026-09-26T10:00:00Z"}. In the dashboard the new secret is shown once in the Your new signing secret window.
For a routine rotation:
Rotate and add the new secret to your verifier, next to the old one.
Keep accepting both until
previous_expires_at.Drop the old secret.
There is only one previous secret: a second rotation inside the 24 hours retires the older one at once. Deliveries may keep using the previous set of secrets for up to 60 seconds after a rotation, which the overlap covers.
Last updated
Was this helpful?