CallingBox-Signature header. Your server
must verify this header on every request before doing any work —
otherwise anyone who knows your URL can forge events.
Anatomy of the signature header
t=<timestamp>— the unix second when CallingBox signed the payload.v1=<hex>— HMAC-SHA256 of"<timestamp>.<raw_body>"using your endpoint’s signing secret. The header may eventually contain multiplev1=values during secret rotations; your code should accept a match against any of them.
Verify in Python
construct_event raises callingbox.BadRequestError on any mismatch,
missing pieces, or timestamp drift greater than 300 seconds.
Verify in TypeScript / Node
Manual verification (any language)
If you aren’t using the SDKs, roll your own with any HMAC-SHA256 library:- Split the header on
,; parse thet=timestamp and everyv1=value. - Check
abs(now - t) <= 300seconds. Reject otherwise. - Compute
HMAC_SHA256(secret, f"{t}.{raw_body}")and lowercase-hex-encode it. - Compare against every
v1=value with a constant-time comparison. Accept if any match.
Always use the raw body
Parse JSON after verification, using the exact bytes the body came in with. If your framework has already parsed the body and re-serialized it, the signature will fail even for legitimate events. In Express useexpress.raw({ type: "application/json" }); in FastAPI read
await request.body().
Secret rotation
Rotate a signing secret with:signing_secret once. Previous signatures
stop verifying immediately, so coordinate the rollout:
- Rotate and grab the new secret.
- Deploy the new secret to your consumers.
- Re-deliver any events that failed verification in the brief window (see Retries).
Per-call vs endpoint secrets
- Account-level endpoints have their own
signing_secret, scoped to that endpoint only. - Per-call
webhook_urldeliveries are signed with the organization’s default secret. Grab it from the dashboard under Webhooks → Default signing secret. Rotating it affects all inline per-call webhooks in the org.