Signature Verification

Primitive signs outbound webhook deliveries so your endpoint can verify that the payload came from Primitive and was not modified in transit.

The header is:

Primitive-Signature: t=<unix-seconds>,v1=<hex-hmac>

Verify the exact raw request body. Do not parse JSON and re-stringify it before verification.

SDK Helpers

import {
  verifyWebhookSignature,
  WebhookVerificationError,
  PRIMITIVE_SIGNATURE_HEADER,
} from '@primitivedotdev/sdk/api';


export async function POST(req: Request) {
  const rawBody = await req.text();
  const signatureHeader = req.headers.get(PRIMITIVE_SIGNATURE_HEADER) ?? '';


  try {
    await verifyWebhookSignature({
      rawBody,
      signatureHeader,
      secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,
    });
  } catch (error) {
    if (error instanceof WebhookVerificationError) {
      return new Response('invalid signature', { status: 401 });
    }
    throw error;
  }


  return Response.json({ ok: true });
}

Manual Flow

  1. Parse the t and v1 parts of Primitive-Signature.
  2. Reject timestamps outside your tolerance window.
  3. Build the signed payload as <timestamp>.<raw-body>.
  4. Compute HMAC-SHA256 with your webhook signing secret.
  5. Compare the expected hex digest to v1 using a constant-time comparison.

Common Mistakes

  • Reading request.json() before verification.
  • Verifying a re-serialized JSON string instead of raw bytes.
  • Comparing strings with normal equality instead of a timing-safe comparison.
  • Returning 4xx for event types you intentionally ignore. Verify first, then return 2xx for ignored but trusted events.

Related Pages