Skip to content

Postern can push inbound mail to your endpoint as HMAC-signed, timestamped webhooks, so you don’t have to poll. The signature lets you verify every delivery with Web Crypto — in Node or at the edge, with no dependency.

{
"url": "https://my-agent.example.com/inbound",
"events": ["message.received"],
"address": "agent7@x4p.mszazu.com" // optional — scope to one mailbox; omit for all
}
Terminal window
curl -sS -X POST "$POSTERN_API_BASE_URL/v1/webhooks" \
-H "Authorization: Bearer $POSTERN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://my-agent.example.com/inbound", "events": ["message.received"] }'
{
"id": "wh_2Lp",
"url": "https://my-agent.example.com/inbound",
"events": ["message.received"],
"secret": "whsec_…" // shown once — store it now
}

Each delivery POSTs a JSON body and carries the signature headers:

POST /inbound HTTP/1.1
Content-Type: application/json
X-Postern-Signature: t=1749751542,v1=3b2a… // timestamp + HMAC-SHA256 over `t.body`
X-Postern-Event: message.received
message.received
{
"event": "message.received",
"inbox": "agent7@x4p.mszazu.com",
"message": {
"id": "msg_8Tz",
"thread_id": "thread_9aB",
"from": "no-reply@acme.test",
"subject": "Verify your email",
"received_at": "2026-06-12T18:05:42Z"
}
}

The signature is HMAC-SHA256(secret, "{t}.{raw_body}"), hex-encoded, with a timestamp to defeat replay. Verify the raw body — don’t re-serialize the JSON first.

inbound-handler.ts
import { verifyWebhookSignature } from "@postern/sdk";
export async function POST(req: Request) {
const payload = await req.text(); // raw body — do not re-serialize
const ok = await verifyWebhookSignature({
payload,
signature: req.headers.get("x-postern-signature")!,
secret: process.env.POSTERN_WEBHOOK_SECRET!,
});
if (!ok) return new Response("bad signature", { status: 400 });
// ... handle the verified message.received event ...
return new Response("ok");
}

Or parseWebhook({ ... }) to verify and JSON-parse in one step (returns null on a bad signature).

  • wait_for_email — pull the next message instead of receiving a push.
  • Inboxesinbound_webhook_url registers a webhook at create time.
  • Errors — status codes.