---
title: "Signature Verification"
description: "Verify Primitive webhook signatures with SDK helpers or the manual HMAC-SHA256 flow."
canonical: "https://docs.primitive.dev/docs/signature-verification"
last-updated: "2026-05-17T00:00:00.000Z"
---

# 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 });
}
```

```
import os
from primitive import WebhookVerificationError, verify_webhook_signature


async def handle(request):
    raw_body = await request.body()
    signature_header = request.headers.get('Primitive-Signature', '')


    try:
        verify_webhook_signature(
            raw_body=raw_body,
            signature_header=signature_header,
            secret=os.environ['PRIMITIVE_WEBHOOK_SECRET'],
        )
    except WebhookVerificationError:
        return {'status': 401, 'body': 'invalid signature'}


    return {'ok': True}
```

```
body, _ := io.ReadAll(r.Body)
signatureHeader := r.Header.Get("Primitive-Signature")


_, err := primitive.VerifyWebhookSignature(primitive.VerifyOptions{
    RawBody:         body,
    SignatureHeader: signatureHeader,
    Secret:          os.Getenv("PRIMITIVE_WEBHOOK_SECRET"),
})
if err != nil {
    http.Error(w, "invalid signature", http.StatusUnauthorized)
    return
}
```


## Manual Flow

- Parse the `t` and `v1` parts of `Primitive-Signature`.

- Reject timestamps outside your tolerance window.

- Build the signed payload as `<timestamp>.<raw-body>`.

- Compute HMAC-SHA256 with your webhook signing secret.

- 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

- [Receiving Mail](/docs/receiving): webhook delivery behavior.

- [Webhook Payload](/docs/webhook-payload): event schema and fields.

- [SDKs](/docs/sdks): high-level receive helpers.

Maintained by the [primitive.dev team](/about) · [Trust & security](/trust)Last updated: May 17, 2026
