Secret format
Macro generates secrets as whsec_ plus 48 hex characters (24 random bytes). You may supply your own secret (16–256 characters) at create time. Reveal or rotate from the dashboard edit flow or Management API credential routes.
Signature algorithm
- Read
X-Macro-Timestamp(Unix seconds) and the raw body string. - Compute HMAC-SHA256 over
{timestamp}.{rawBody}using the endpoint secret. - Compare to
X-Macro-Signature, which looks likesha256=<hex digest>, with a timing-safe equality check. - Reject if the absolute skew between now and the timestamp exceeds 300 seconds (5-minute replay window).
import { createHmac, timingSafeEqual } from 'node:crypto'
const REPLAY_WINDOW_SEC = 300
export function verifyMacroWebhook({ secret, timestamp, rawBody, signature }) {
const now = Math.floor(Date.now() / 1000)
const ts = Number.parseInt(timestamp, 10)
if (!Number.isFinite(ts) || Math.abs(now - ts) > REPLAY_WINDOW_SEC) return false
const expected =
'sha256=' +
createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signature)
if (a.length !== b.length) return false
return timingSafeEqual(a, b)
}# timestamp and raw body must match the request exactly
TIMESTAMP="1710000000"
BODY='{"id":"evt_…","object":"event"}'
SECRET="whsec_…"
printf '%s' "${TIMESTAMP}.${BODY}" \
| openssl dgst -sha256 -hmac "$SECRET" \
| awk '{print "sha256="$2}'Idempotency
Prefer X-Macro-Event-Id (stable per logical event) for de-duplication. X-Macro-Delivery-Id uniquely identifies each attempt, including retries and manual resends.
Rotate the secret
Rotate in Macro
Dashboard edit → rotate, or
POST /management/webhooks/:webhookId/rotate-secret(settings.webhook:write). Copy the newwhsec_…value.Update your receiver
Deploy the new secret. During cutover you can briefly accept either old or new until traffic uses the rotated credential.
/management/webhooks/:webhookId/credentialmt_Reveal the current signing secret (settings.webhook:read).
/management/webhooks/:webhookId/rotate-secretmt_Issue a new secret and return it once (settings.webhook:write).
Do
- Verify signature + timestamp before parsing business logic.
- Store secrets in a vault / env var — never in client-side code.
- Log delivery ids when debugging failed verifications.
Don’t
- Don’t trust
X-Macro-Triggeralone without a valid signature. - Don’t re-stringify JSON before verifying — whitespace must match the raw body.
- Don’t skip the replay window check in production.