Signature Verification
Every webhook is signed. Verify the signature before trusting the payload.
The signature header
Every webhook includes a Primitive-Signature header with this format:
t=1734523200,v1=5257a869e7ecebeda32aff1deadbeef951cad7e77a0e56ff536d0ce8e108d8bd
t- Unix timestamp when the signature was generatedv1- HMAC-SHA256 signature
Verifying with the SDK
Use these lower-level helpers when you want to inspect the signature before parsing the payload; otherwise reach for the SDK's high-level webhook helper.
import { verifyWebhookSignature, WebhookVerificationError } from '@primitivedotdev/sdk';try {verifyWebhookSignature({rawBody,signatureHeader: request.headers.get('Primitive-Signature') ?? '',secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,});// Signature is valid} catch (error) {if (error instanceof WebhookVerificationError) {console.error('Verification failed:', error.code);}throw error;}
Manual verification
If you are not using the SDK, the verification flow is the same in every language:
- Parse the header and extract
tandv1. - Reject timestamps older than 5 minutes to limit replay attacks.
- Compute an HMAC-SHA256 signature of
{timestamp}.{rawBody}using your webhook secret. - Compare the provided and expected signatures with a constant-time check (Node
crypto.timingSafeEqual, Pythonhmac.compare_digest, Gohmac.Equal). A regular string compare leaks bytes via timing. - Always use the exact raw request body, never re-serialized JSON.
Standard Webhooks compatibility
Primitive also accepts and emits the Standard Webhooks format (webhook-id, webhook-timestamp, and webhook-signature headers). The SDK transparently verifies whichever style your endpoint receives, so generic Standard Webhooks libraries also work.
Webhook secret
Find it under Settings → Webhooks. Rotate from the same screen; the old value is invalidated immediately, so update handlers first.
Error codes
Verification failures use stable error codes across the SDKs:
| Code | Description |
|---|---|
INVALID_SIGNATURE_HEADER | Missing or malformed Primitive-Signature header |
TIMESTAMP_OUT_OF_RANGE | Timestamp is too old (possible replay attack) |
SIGNATURE_MISMATCH | Signature doesn't match expected value |
MISSING_SECRET | No webhook secret was provided |