Developers

Delivery webhooks

Get notified when mail is delivered, bounced, or deferred. Event types, REST routes, payloads, and signature verification.

Overview

Webhooks notify your application when mail sent through xMailCloud changes state — for example when a message is delivered or bounces. Each delivery is a signed HTTP POST to a URL you register.

Available on Cloud Business and Cloud Growth. Register endpoints in Workspace → Webhooks or use the REST routes below while signed in.

Event types

EventWhen it fires
mail.acceptedSMTP server accepted the message
mail.deliveredPostfix reports successful delivery
mail.deferredTemporary delivery failure (Postfix will retry)
mail.bouncedHard bounce / DSN
mail.rejectedRejected by policy or remote server
mail.failedSMTP send failed before accept
mail.testSent when you click Send test or POST …/test

Manage endpoints

Routes below require a signed-in customer session. Use them from server-side code after authenticating at account login.

POST /api/webhooks/endpoints

Create a webhook endpoint. Response includes a signing secret — store it securely.

Content-Type: application/json

{
  "url": "https://example.com/webhooks/xmailcloud",
  "description": "Production app",
  "events": ["mail.accepted", "mail.delivered", "mail.bounced"]
}

// 201 Created
{
  "endpoint": {
    "id": "uuid",
    "url": "https://example.com/webhooks/xmailcloud",
    "events": ["mail.accepted", "mail.delivered", "mail.bounced"],
    "active": true,
    "description": "Production app",
    "secret": "whsec_...",
    "createdAt": 1755678900123,
    "updatedAt": 1755678900123
  }
}

GET /api/webhooks/endpoints

List your endpoints and the available event names.

// 200 OK
{
  "events": [
    "mail.accepted",
    "mail.delivered",
    "mail.deferred",
    "mail.bounced",
    "mail.rejected",
    "mail.failed"
  ],
  "endpoints": [ /* same shape as create response */ ]
}

PATCH /api/webhooks/endpoints/{id}

Update URL, events, description, or pause/resume delivery.

Content-Type: application/json

{
  "active": false,
  "events": ["mail.delivered", "mail.bounced", "mail.failed"]
}

// 200 OK — { "endpoint": { ... } }

DELETE /api/webhooks/endpoints/{id}

Remove a webhook endpoint.

// 200 OK
{ "ok": true }

POST /api/webhooks/endpoints/{id}/test

POST a signed test payload to your URL (see test payload below).

// 200 OK
{ "ok": true, "message": "Test webhook sent." }

Webhook delivery payload

xMailCloud POSTs JSON to your URL when a subscribed event occurs. Your endpoint should return HTTP 2xx quickly; retries use exponential backoff.

{
  "id": "event-uuid",
  "type": "mail.delivered",
  "createdAt": 1755678900123,
  "data": {
    "messageId": "outbound-message-uuid",
    "status": "delivered",
    "detail": "postfix log line...",
    "queueId": "4bWnHY1QHZz5Q",
    "from": "notifications@malegado.com",
    "to": "user@example.com",
    "subject": "Welcome",
    "source": "campaign",
    "smtpMessageId": "<xmc.uuid@xmailcloud.local>",
    "postfixQueueId": "4bWnHY1QHZz5Q"
  }
}

source is one of webmail, campaign, api, or platform.

Test payload

{
  "id": "test_1755678900123",
  "type": "mail.test",
  "createdAt": 1755678900123,
  "data": {
    "message": "This is a test event from xMailCloud.",
    "endpointId": "your-endpoint-uuid"
  }
}

Request headers

Every delivery includes these headers alongside the JSON body:

Content-Type: application/json
User-Agent: xMailCloud-Webhooks/1.0
X-XMailCloud-Event: mail.delivered
X-XMailCloud-Timestamp: 1755678900
X-XMailCloud-Signature: t=1755678900,v1=hex_hmac_sha256

Signature verification

Verify X-XMailCloud-Signature with your endpoint signing secret. The signed string is {timestamp}.{raw_json_body}. Reject timestamps older than 5 minutes to limit replay attacks.

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

function verifySignature(input: {
  secret: string;
  timestamp: number;
  body: string;
  signature: string;
}) {
  const payload = `${input.timestamp}.${input.body}`;
  const expected = createHmac("sha256", input.secret)
    .update(payload)
    .digest("hex");
  const provided = input.signature.toLowerCase();
  return (
    expected.length === provided.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(provided))
  );
}

// Express example — read raw body before JSON.parse
app.post(
  "/webhooks/xmailcloud",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const body = req.body.toString("utf8");
    const header = req.header("X-XMailCloud-Signature") || "";
    const match = header.match(/^t=(\d+),v1=([a-f0-9]+)$/i);
    if (!match) return res.sendStatus(400);

    const timestamp = Number(match[1]);
    const age = Math.abs(Date.now() - timestamp * 1000);
    if (age > 5 * 60 * 1000) return res.sendStatus(401);

    const ok = verifySignature({
      secret: process.env.XMAILCLOUD_WEBHOOK_SECRET!,
      timestamp,
      body,
      signature: match[2],
    });
    if (!ok) return res.sendStatus(401);

    const event = JSON.parse(body);
    // handle event.type and event.data
    res.sendStatus(200);
  },
);

Message correlation

Tracked sends include an X-XMailCloud-Message-Id header (UUID) and an SMTP Message-ID like <xmc.{uuid}@xmailcloud.local>. The webhook data.messageId matches that UUID so you can tie delivery events back to your app's send call.

Send mail HTTP API