Signature Verification
Primitive signs every webhook request so you can verify it came from us. This prevents attackers from sending fake webhooks to your endpoint.
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
The easiest path is still to use your SDK's high-level webhook helper. Reach for these lower-level verification helpers when you want to inspect the signature before parsing the payload.
Node.js uses WebhookVerificationError for structured verification failures.
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 timing-safe comparison.
- Always use the exact raw request body, never re-serialized JSON.
Finding your webhook secret
Your webhook secret is in the Webhooks section of your dashboard. Store it securely in PRIMITIVE_WEBHOOK_SECRET or an equivalent secret manager value, and never commit it to source control.
Rotating your secret
You can rotate your webhook secret from the dashboard at any time. When you rotate, the old secret is immediately invalidated. Update your webhook handlers before rotating to avoid verification failures.
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 |