Search the docs

Find a page, a section, or an endpoint.

Verifying a signature

HMAC over the timestamp and the body. Before anything acts on a payload.

Do this before you act on anything. We sign the timestamp along with the body, so a captured request cannot be replayed forever.

The headers

http
x-pidgeon-timestamp: 1757760062
x-pidgeon-signature: v1=4f1a9c…

The signature is HMAC-SHA256 over the exact bytes of timestamp + . + body, hex-encoded, keyed with the secret that webhook was created with.

Verify it

import { verifyWebhook } from '@pidgeon-ai/sdk';

export async function POST(req: Request) {
const { type, data } = await verifyWebhook({
  rawBody: await req.text(),   // raw, not parsed
  headers: req.headers,
  secret: process.env.PIDGEON_WEBHOOK_SECRET!,
});

if (type === 'message.received') {
// …
}

return new Response(null, { status: 204 });
}

Three ways to get this wrong

Parsing the body first. The signature is over bytes. Parsing JSON and re-serialising it changes key order, whitespace and unicode escapes, and the signature then fails on a request that is perfectly valid. Read the raw body, verify, then parse. This is the mistake, and it produces an error saying the request is not signed when it plainly is.

Comparing with ===. String comparison returns early on the first differing byte, which leaks the signature a character at a time to anybody patient. Use a timing-safe compare — crypto.timingSafeEqual, hmac.compare_digest, or the SDK, which does it for you.

Ignoring the timestamp. A signature proves the body came from us. It does not prove when. Anything older than five minutes is refused as a replay; the tolerance is a parameter on verifyWebhook, and widening it should be a decision rather than a default.

Answer 2xx quickly

Do the work after you reply, not before. A handler that takes eleven seconds is a handler we time out and retry, and the retry does the same work again. Acknowledge, queue, return.

An event type you do not recognise is not an error — return 204 and ignore it, or we will deliver it seven more times.