Skip to content

Verify Postmark webhooks

Verify that a webhook really came from Postmark before acting on it.

Identifier 'postmark'
Import import { verifyPostmark } from 'verihook/postmark'
Headers authorization (Basic)
Signature No signature. HTTP Basic auth from credentials in the webhook URL.
Replay window None (the provider sends no timestamp)
result.eventType the body’s RecordType, e.g. Delivery

The user:pass you put in the webhook URL (https://user:pass@example.com/webhooks/postmark) in the Postmark server’s Webhooks settings, passed as "user:pass".

Store it in an environment variable (POSTMARK_WEBHOOK_CREDENTIALS below) and never commit it.

import { verifyPostmark } from 'verihook/postmark';
export async function POST(request: Request) {
const result = await verifyPostmark(request, process.env.POSTMARK_WEBHOOK_CREDENTIALS!);
if (!result.valid) {
console.warn(result.code, result.reason, result.hint);
return new Response('Invalid signature', { status: 401 });
}
console.log(result.eventType, result.event);
return Response.json({ received: true });
}

request can be a Fetch Request or { headers, body, url? } with the raw body. verifyWebhook('postmark', request, secret) from verihook does the same. Read why the raw body matters if verification fails behind a body parser.

app/api/webhooks/postmark/route.ts
import { createWebhookHandler } from 'verihook/next';
export const POST = createWebhookHandler('postmark', process.env.POSTMARK_WEBHOOK_CREDENTIALS!, async (payload, result) => {
// Runs only for a valid signature
console.log(result.eventType, result.event);
});

Each adapter responds 401 to an invalid signature, 413 to a body over maxBodySize (2 MB by default) and 200 to a duplicate when you pass a dedupeStore.

  • Postmark doesn’t sign webhooks. It also recommends allowlisting its IP addresses.

signWebhook from verihook/testing builds a correctly signed Postmark request for your tests:

import { signWebhook } from 'verihook/testing';
import { verifyPostmark } from 'verihook/postmark';
const hook = await signWebhook('postmark', {
secret: 'postmark_user:postmark_pass',
payload: { id: 'evt_test' },
});
const result = await verifyPostmark(
{ headers: hook.headers, body: hook.body, url: hook.url },
hook.secret,
);
// result.valid === true

From the command line, npx verihook simulate postmark --url http://localhost:3000/webhooks/postmark sends one to your local server.

See Testing your handlers for supertest and Fetch Request examples.

Failed results carry a code and often a hint with the likely cause. See Troubleshooting for each error code.

Postmark’s webhook documentation