/integrations/* must carry:
| Header | Value |
|---|---|
x-partner-slug | Your slug, e.g. acme. |
x-signature | t=<unix-seconds>,v1=<hex-hmac-sha256> |
content-type | application/json (omit for empty-body methods). |
Signing algorithm
timestampis integer seconds since the Unix epoch, captured at the moment of signing. Our tolerance is ±5 minutes (300 seconds).rawBodyis the exact bytes of the request body. For methods with no body (DELETE), sign the empty string — e.g.${timestamp}.- Use a constant-time comparator when validating our outbound webhooks.
Rotation
The header parser accepts multiplev1= values:
Example
Common signing mistakes
Signing the parsed/serialized body instead of the raw bytes
Signing the parsed/serialized body instead of the raw bytes
If you
JSON.parse() then JSON.stringify() the body before signing, the
bytes can differ (key order, whitespace) and the signature won’t match
what arrives over the wire. Sign the exact string you’ll send.Wrong timestamp scale
Wrong timestamp scale
t is seconds, not milliseconds. Math.floor(Date.now() / 1000) is
correct; Date.now() alone will fail the ±5 minute tolerance.Stripping the leading dot for empty bodies
Stripping the leading dot for empty bodies
For DELETE / empty-body requests, the signed payload is
${timestamp}. —
don’t omit the trailing dot, otherwise our verifier and yours will
compute different hashes.Hex case mismatch
Hex case mismatch
The hex output is conventionally lowercase. Some libraries default to
uppercase. We normalize, but it’s a useful thing to check first if you
see
invalid_signature errors.