Webhook handlers are one place where a clean-looking coding-agent implementation can be wrong in a provider-specific way.
In a 64-attempt Claude Code transactional-email benchmark, Postmark was selected eight times for delivery webhooks. Seven of those implementations tried to verify an HMAC or signature that Postmark's canonical webhook documentation says it does not provide.
The repair is not to remove webhook security. It is to use the mechanism supported by the selected provider.
Resend: verify the signed raw body
Resend documents signed webhook requests through Svix headers. Read the body as text before any JSON parsing and verify it with the provider signing secret.
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY!);
export async function POST(request: Request) {
const payload = await request.text();
const event = resend.webhooks.verify({
payload,
headers: {
id: request.headers.get("svix-id")!,
timestamp: request.headers.get("svix-timestamp")!,
signature: request.headers.get("svix-signature")!,
},
webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,
});
// svix-id is the delivery identifier. Persist it before side effects.
const deliveryId = request.headers.get("svix-id")!;
await enqueueOnce(deliveryId, event);
return new Response(null, { status: 200 });
}
enqueueOnce is application code: enforce a unique constraint on the delivery ID and process the event asynchronously. Resend documents at-least-once delivery, so duplicate handling is required even after signature verification.
Postmark: authenticate the endpoint, then validate and deduplicate
Postmark's canonical webhook overview currently says HMAC webhook signature verification is not supported. It recommends HTTPS with Basic Authentication and optionally allowlisting Postmark's webhook IP ranges.
Configure the webhook with HTTP authentication, then verify the Authorization header before parsing or acting on the payload.
import { timingSafeEqual } from "node:crypto";
function safeEqual(left: string, right: string): boolean {
const a = Buffer.from(left);
const b = Buffer.from(right);
return a.length === b.length && timingSafeEqual(a, b);
}
type PostmarkEvent = {
RecordType: string;
MessageID: string;
Description?: string;
};
export async function POST(request: Request) {
const expected = `Basic ${Buffer.from(
`${process.env.POSTMARK_WEBHOOK_USER}:${process.env.POSTMARK_WEBHOOK_PASSWORD}`,
).toString("base64")}`;
if (!safeEqual(request.headers.get("authorization") ?? "", expected)) {
return new Response("Unauthorized", { status: 401 });
}
const event = (await request.json()) as PostmarkEvent;
if (!event.MessageID || !event.RecordType) {
return new Response("Invalid event", { status: 400 });
}
// MessageID plus RecordType is a practical event-specific idempotency key.
await enqueueOnce(`${event.RecordType}:${event.MessageID}`, event);
return new Response(null, { status: 200 });
}
The example intentionally does not invent a Postmark signature header. If you also use an IP allowlist, treat it as an additional control rather than a replacement for HTTPS, endpoint authentication, payload validation, and idempotency.
Provider-specific checklist
| Concern | Resend | Postmark |
|---|---|---|
| Authenticity mechanism | Svix signature over raw body | HTTPS + Basic Auth; optional IP allowlist |
| Parse body | After signature verification | After endpoint authentication |
| Duplicate key | svix-id |
Event-specific key such as RecordType:MessageID
|
| Delivery behavior | At least once; retries documented | Retries on non-200; 403 stops retries |
| Common coding-agent error | Parsing JSON before verification | Inventing an HMAC/signature header |
Why this distinction matters
The same research panel showed that provider recommendations and implementations can diverge. ChatGPT Search recommended Postmark for delivery-webhook prompts in 4/4 fresh conversations, while Claude Chat recommended Resend in 4/4. A recommendation is not proof that the resulting provider-specific code will work.
Validate the selected provider's authentication model before accepting generated webhook code.
Sources and boundaries
- Postmark Webhooks overview
- Postmark Webhooks API
- Resend: Verify webhook requests
- Resend: Managing webhooks
- AgentAnalytics benchmark evidence
The benchmark used available static and provider-specific validators; it did not send live email or call provider APIs. The snippets above are implementation skeletons and assume a durable enqueueOnce function supplied by the application.
No included provider paid for this article, inclusion, rank, favorable wording, or removal of a result.
Top comments (0)