Developers
Delivery webhooks
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
| Event | When it fires |
|---|---|
| mail.accepted | SMTP server accepted the message |
| mail.delivered | Postfix reports successful delivery |
| mail.deferred | Temporary delivery failure (Postfix will retry) |
| mail.bounced | Hard bounce / DSN |
| mail.rejected | Rejected by policy or remote server |
| mail.failed | SMTP send failed before accept |
| mail.test | Sent 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.
