Skip to content
Developer platform · Events

Receive signed email events

Webhooks are delivered at least once. Verify before parsing, deduplicate by event ID, and return a 2xx response within five seconds.

Endpoint verification handshake

Before saving an endpoint, Omnero Mail sends webhook.endpoint_verification. Parse the JSON and return { "challenge": payload.data.challenge } with HTTP 2xx within five seconds. Redirects, timeouts, non-2xx responses, invalid JSON, or a different challenge reject creation. The handshake proves control of a working handler; its signature cannot be trusted yet because the secret is only revealed after successful creation.

                        const payload = JSON.parse(rawBody);
if (payload.type === 'webhook.endpoint_verification') {
  return Response.json({ challenge: payload.data.challenge });
}
                      

Verify HMAC-SHA256 over the raw bytes

For every operational event, read the three MailDeck-Webhook headers before parsing. Build id + "." + timestamp + "." + rawBody, calculate HMAC-SHA256 with your whsec_ secret, and compare v1=<hex> in constant time. Reject missing headers, timestamps more than five minutes from your clock, malformed hex, or signature mismatch. Never recreate the body with JSON.stringify: even equivalent JSON produces different bytes.

                        import { createHmac, timingSafeEqual } from 'node:crypto';

const id = request.headers.get('maildeck-webhook-id')!;
const timestamp = request.headers.get('maildeck-webhook-timestamp')!;
const supplied = request.headers.get('maildeck-webhook-signature')!;
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) throw new Error('stale webhook');

const expected = createHmac('sha256', process.env.MAILDECK_WEBHOOK_SECRET!)
  .update(`${id}.${timestamp}.${rawBody}`)
  .digest('hex');
const received = supplied.startsWith('v1=') ? supplied.slice(3) : '';
const valid = /^[a-f0-9]{64}$/.test(received) && timingSafeEqual(
  Buffer.from(received, 'hex'), Buffer.from(expected, 'hex')
);
if (!valid) throw new Error('invalid webhook signature');
                      

Delivery envelope and headers

Operational requests use Content-Type: application/json and User-Agent: MailDeck-Webhooks/1.0. The JSON envelope contains id, type, apiVersion, occurredAt, and data. data always includes the canonical object type and resource id plus event-specific fields; consumers must ignore unknown fields and event types for forward compatibility.

  • MailDeck-Webhook-Id — stable event ID and deduplication key
  • MailDeck-Webhook-Timestamp — Unix seconds used in replay protection
  • MailDeck-Webhook-Signature — v1=<64 lowercase hex HMAC>

Encryption at rest versus signature in transit

Omnero Mail generates a random whsec_ value and reveals it once. Internally, the secret is stored as a versioned AES-256-GCM envelope with a fresh 96-bit IV and authentication tag. The dispatcher decrypts it only in worker memory, signs the exact outgoing bytes, and discards the plaintext with the invocation. The event body is not application-encrypted: HTTPS protects transport, while HMAC proves authenticity and integrity. Receivers verify the signature; they never decrypt the JSON payload.

Event catalog

Subscribe to individual types or * for every supported type. Message lifecycle events are message.received, message.queued, message.sent, message.delivered, message.bounced, message.failed, and message.updated. Resource events are mailbox.created, mailbox.updated, mailbox.suspended, and domain.health_changed.

Design for at-least-once delivery

Only parse and process the event after signature verification. In one durable transaction, store the event ID under a unique constraint and enqueue or apply your side effect. Repeated delivery keeps the same ID and is safe to acknowledge. Return 2xx after durable acceptance—not merely after reading the request.

  • No global ordering guarantee
  • Five-second response deadline
  • Automatic retries and dead-letter retention
  • Manual replay from the integration activity view

Retries, terminal failures, and endpoint health

Any timeout, unsafe URL resolution, redirect, network failure, or non-2xx response counts as a failed attempt. Omnero Mail records the failure and retries through the standard queue up to five delivery attempts. Exhausted deliveries become dead and remain available for manual replay. Ten consecutive endpoint failures disable the endpoint; a successful delivery resets the consecutive-failure counter.

Recover after downtime

Use GET /v1/events?limit=25&cursor=<ISO timestamp> with messages:read to reconcile canonical history. Process items, checkpoint nextCursor only after durable acceptance, and continue until it is null. Webhooks optimize latency; the events endpoint restores completeness without assuming global delivery order.

Exercise the pipeline with sandbox events

From Dashboard → Integrations, choose a sandbox application and an event type. Omnero Mail creates a synthetic canonical event marked demo: true, sends it only to that application’s subscribed endpoints, and applies the same HMAC, retry, activity, and replay behavior as an operational delivery.

Next step

Create the first mailbox at no cost.

Start freeLog in