Signed message format
The signature covers the signature contract fields and the raw body:event_id segment is the value of the X-Turnkey-Event-Id header. Other delivery headers such as organization ID and event type are not part of the signed message.
Key discovery
Turnkey publishes webhook signing keys at a public JWKS endpoint. Use this endpoint to fetch the Ed25519 public key needed to verify webhook signatures. Match thekid field in the response to the X-Turnkey-Signature-Key-Id header on each delivery:
The JWKS endpoint returns standard
Cache-Control headers. Cache the response according to those headers and refresh on expiry. If a delivery arrives with a kid that does not match any cached key, refetch the JWKS before rejecting the delivery. This avoids stale-cache failures during key rotation while still allowing efficient caching.
SDK verification helper
The@turnkey/crypto package (v2.10.0+) exports verifyTurnkeyWebhookSignature, which handles signed-input reconstruction, timestamp freshness, and Ed25519 verification. The helper accepts caller-provided verification keys and returns a typed result instead of throwing.
Fetch the JWKS endpoint, convert each key’s base64url-encoded x field to hex, and pass the result as verificationKeys:
Manual verification
If you are not using the TypeScript SDK, verify signatures manually:- Fetch the JWKS from
https://api.turnkey.com/public/v1/discovery/webhooks/jwks. - Find the key whose
kidmatches theX-Turnkey-Signature-Key-Idheader. Base64url-decode thexfield to obtain the 32-byte Ed25519 public key. - Reconstruct the signed input by concatenating the header values and raw body in the format:
v1.ed25519.<key_id>.<timestamp_ms>.<event_id>.<raw_body>. - Hex-decode the
X-Turnkey-Signatureheader to obtain the 64-byte Ed25519 signature. - Verify the Ed25519 signature over the signed input bytes using the public key from step 2.
- Check that
X-Turnkey-Timestampis within an acceptable freshness window (e.g. 5 minutes) to guard against replay attacks.