Skip to main content

Verify Webhook Signatures

Every request carries a Vocci-Signature header, signed with the secret shown on the endpoint's edit screen. Reject any request whose signature does not match.

Vocci-Signature: t=1760000000,v1=5257a869…
  • t is the Unix time, in seconds, at which the request was signed.
  • v1 is the lowercase hex HMAC-SHA256 of <t>.<raw request body>, keyed with the whole secret string.

Steps​

  1. Read the raw request body before parsing it. Re-serialized JSON will not match.
  2. Split the header into t and v1.
  3. Compute HMAC-SHA256(secret, t + "." + rawBody) as hex.
  4. Compare it with v1 in constant time.
  5. Reject the request if t is more than 5 minutes from your clock. Each attempt, including a retry, is signed afresh, so a retry is never stale.

Node.js​

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;

const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
return (
expected.length === parts.v1.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
);
}

Python​

import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(item.split("=", 1) for item in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
signed = parts["t"].encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])

Zapier and similar tools​

Catch Hook triggers do not check signatures on their own. Keep the URL secret, since it is the credential, and use Send test event to see that the zap receives the fields. If you need signature checks, put a small code step first, or receive the webhook in your own service.

Changing the secret​

Open the endpoint, replace the secret, and update your receiver at the same time. Deliveries in flight use the new secret from the moment you save.