Skip to content
meruma Developers

Developers · Webhooks

Verify the signature

Every event is signed with the webhook's secret. Verify it before trusting its content.

The headers

Events follow Standard Webhooks: any library for that specification verifies them with the secret you get when you create the webhook.

  • webhook-id: the event id, the same on every retry. Use it to avoid processing an event twice.
  • webhook-timestamp: when it was sent, in seconds. Discard events more than 5 minutes old.
  • webhook-signature: v1, followed by the base64 HMAC-SHA256 of id.timestamp.body.

Verify in Node, with no libraries

const crypto = require('crypto');

const signed = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${rawBody}`;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key).update(signed).digest('base64');
const valid = headers['webhook-signature']
  .split(' ')
  .some((part) => part === `v1,${expected}`);

Use the body exactly as it arrived (rawBody), before parsing it as JSON: any whitespace change breaks the signature.

Verify in Python

import base64, hashlib, hmac

signed = f"{h['webhook-id']}.{h['webhook-timestamp']}.".encode() + raw_body
key = base64.b64decode(secret.removeprefix("whsec_"))
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
valid = any(part == f"v1,{expected}" for part in h["webhook-signature"].split())

Rotate the secret

You can rotate the secret from Webhooks. The previous one keeps signing for 24 hours and the header carries both signatures, so your system keeps working while you update it.

Reviewed on