Webhooks
Receive signed, retried HTTPS events when mail or domain state changes.
Create an endpoint
Create a webhook in Settings → Developers or with the API. The URL must use HTTPS and must not resolve to a private or unsafe network destination.
const webhook = await mail.createWebhook({
url: "https://example.com/startupmail/events",
description: "Production message worker",
eventTypes: ["message.received", "message.failed"],
});
console.log(webhook.signingSecret); // store now; shown once
Request headers
Each delivery is a JSON POST with:
StartupMail-Event— the event type.StartupMail-Delivery— a unique delivery ID.StartupMail-Signature—t=<unix-seconds>,v1=<hex-hmac>.User-Agent—StartupMail-Webhooks/1.0.
Verify the signature
Compute HMAC-SHA256 over <timestamp>.<raw-request-body> using the endpoint secret, compare it in constant time, and reject timestamps outside your replay window. Do not parse and reserialize the JSON before verification.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, header: string, secret: string) {
const [timestampPart, signaturePart] = header.split(",");
const timestamp = timestampPart.replace("t=", "");
const received = Buffer.from(signaturePart.replace("v1=", ""), "hex");
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
return received.length === expected.length && timingSafeEqual(received, expected);
}
Event payloads
message.received includes messageId, mailboxId, threadId, sender, subject, and hasAttachments. Delivery events identify the message and its status. domain.verified identifies the domain that completed verification.
Use event IDs as signals to retrieve the current resource rather than assuming the event body is a complete message object.
Delivery and retries
Return any 2xx status quickly. Non-success responses and network errors retry up to five delivery attempts with increasing delay. Your handler must be idempotent because a request can be delivered more than once.