X-FPT-Signature header. Verifying this signature on every request proves the payload genuinely came from us and hasn’t been tampered with in transit.
The signature header
| Key | Meaning |
|---|---|
t | Unix timestamp (seconds since epoch) when we computed the signature. Use this to reject replay attempts older than your tolerance window (we suggest 5 minutes). |
v1 | Hex-encoded HMAC-SHA256 of "{t}.{rawBody}" using your subscription’s signing secret. |
The signing scheme
{t}is the same timestamp value from the header{rawBody}is the exact request body, byte-for-byte — don’t re-serialize the JSON (whitespace changes break the signature)secretis the 32+ character secret revealed once when you click Generate Key on your subscription
Verification recipe (language-agnostic)
Reject ancient timestamps
Check
now - t > 300 (5 minutes). Reject as 401 — defends against replay attacks if a signature ever leaks.Read the raw body, exactly as received
Don’t parse, don’t pretty-print, don’t strip whitespace. Capture the bytes.
Constant-time compare
Use a constant-time comparison function (
hmac.compare_digest in Python, crypto.timingSafeEqual in Node, CryptographicOperations.FixedTimeEquals in .NET). Never use == on the strings — it leaks the secret via timing side channels.Code samples
Key rotation
Click Rotate Key on your subscription’s row in Settings → Webhook Endpoints to issue a new secret. The dialog reveals the new secret once — save it.Grace period after rotation: for 24 hours after rotation, FPT signs deliveries with the NEW secret only — the old one is invalidated immediately. Plan rotations carefully:
- Get the new secret in the FPT admin
- Deploy the new secret to your endpoint config
- Verify a test event lands successfully
- Done — the rotation is complete
Common verification mistakes
Re-parsing the JSON before signing
Re-parsing the JSON before signing
Your framework probably parses the body into a
req.body object before your handler runs. Capture the raw bytes before that happens. If you sign JSON.stringify(req.body), the whitespace or field ordering will differ from what we signed and you’ll always fail verification.Using == or string equality
Using == or string equality
String comparison short-circuits on the first differing character — that timing leak lets an attacker brute-force the signature one character at a time. Always use the constant-time comparison helper your platform provides.
Forgetting to reject old timestamps
Forgetting to reject old timestamps
Without a timestamp tolerance check, a leaked signature is valid forever. The
t= field in the header exists specifically so you can reject replays older than ~5 minutes.Treating the secret as a password (storing in clear text, logging it)
Treating the secret as a password (storing in clear text, logging it)
Treat it like any other API credential. Store in your secrets manager, never log it, rotate periodically.