Appearance
Pro Webhooks
Receive HTTP POST callbacks when emails arrive. No polling required.
How Webhooks Work
- You register a webhook URL and select events
- When an event occurs, Mail.cx sends an HTTP POST to your URL
- Your server responds with
2xxto acknowledge receipt - Failed deliveries are retried with exponential backoff
Webhook Payload
When an email.received event fires, the payload looks like:
json
{
"id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "email.received",
"created_at": "2025-01-15T10:30:00Z",
"data": {
"email_id": "f5e6d7c8-a1b2-3c4d-e5f6-789012345678",
"account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"address": "info@yourdomain.com",
"from": "Example <noreply@example.com>",
"sender": "noreply@example.com",
"subject": "Verify your email",
"preview_text": "Click the link below to verify...",
"size": 4523,
"created_at": "2025-01-15T10:30:00Z"
}
}Signature Verification
Each webhook request includes these headers:
| Header | Description |
|---|---|
X-Webhook-ID | Unique event ID (e.g. evt_...) |
X-Webhook-Timestamp | Unix timestamp (seconds) |
X-Webhook-Signature | HMAC-SHA256 signature: sha256=... |
The signature is computed over timestamp + "." + body using your webhook secret:
javascript
const crypto = require('crypto');
function verifySignature(body, timestamp, signature, secret) {
const payload = timestamp + '.' + body;
const expected = 'sha256=' +
crypto.createHmac('sha256', secret).update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Usage in Express:
app.post('/webhook', (req, res) => {
const body = req.rawBody; // raw string
const timestamp = req.headers['x-webhook-timestamp'];
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(body, timestamp, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process event...
res.sendStatus(200);
});python
import hmac, hashlib
def verify_signature(body: bytes, timestamp: str, signature: str, secret: str) -> bool:
payload = timestamp.encode() + b'.' + body
expected = 'sha256=' + hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Create a Webhook
POST /pro/api/webhooksRequest
bash
curl -X POST https://api.mail.cx/pro/api/webhooks \
-H "Authorization: Bearer tm_pro_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://yourapp.com/webhook","events":["email.received"]}'| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL to receive callbacks |
events | string[] | Yes | Events to subscribe to |
Available Events
| Event | Description |
|---|---|
email.received | New email delivered to a mailbox |
Response 201
json
{
"id": "w1e2b3h4-o5o6-k7i8-d9e0-abcdef123456",
"url": "https://yourapp.com/webhook",
"events": ["email.received"],
"secret": "whsec_aBcDeFgHiJkLmNoPqRsTuVwXyZ",
"status": "active",
"created_at": "2025-01-15T10:00:00Z"
}WARNING
The secret is only returned when the webhook is created. Save it securely — you'll need it to verify signatures.
Errors
| Status | Error Code | Description |
|---|---|---|
| 400 | invalid_url | URL must be HTTPS |
| 403 | webhook_limit_reached | Webhook limit reached |
List Webhooks
GET /pro/api/webhooksRequest
bash
curl https://api.mail.cx/pro/api/webhooks \
-H "Authorization: Bearer tm_pro_xxxxxxxxxxxx"Response 200
json
{
"webhooks": [
{
"id": "w1e2b3h4-o5o6-k7i8-d9e0-abcdef123456",
"url": "https://yourapp.com/webhook",
"events": ["email.received"],
"status": "active",
"failure_count": 0,
"last_triggered_at": "2025-01-15T10:30:00Z",
"created_at": "2025-01-15T10:00:00Z"
}
]
}| Field | Type | Description |
|---|---|---|
status | string | active or disabled |
failure_count | integer | Consecutive delivery failures |
last_triggered_at | string? | Last successful delivery timestamp |
INFO
Webhooks are automatically disabled after 10 consecutive failures. Delete and re-create to re-enable.
Delete a Webhook
DELETE /pro/api/webhooks/{id}Request
bash
curl -X DELETE https://api.mail.cx/pro/api/webhooks/w1e2b3h4-... \
-H "Authorization: Bearer tm_pro_xxxxxxxxxxxx"Response 204
No content.
Rotate Webhook Secret
POST /pro/api/webhooks/{id}/rotateGenerate a new signing secret. The old secret is invalidated immediately.
Request
bash
curl -X POST https://api.mail.cx/pro/api/webhooks/w1e2b3h4-.../rotate \
-H "Authorization: Bearer tm_pro_xxxxxxxxxxxx"Response 200
json
{
"id": "w1e2b3h4-o5o6-k7i8-d9e0-abcdef123456",
"secret": "whsec_NewSecretKeyHere123456"
}List Deliveries
GET /pro/api/webhooks/{id}/deliveriesView recent delivery attempts for debugging.
Request
bash
curl https://api.mail.cx/pro/api/webhooks/w1e2b3h4-.../deliveries \
-H "Authorization: Bearer tm_pro_xxxxxxxxxxxx"Response 200
json
{
"deliveries": [
{
"id": "d1e2l3i4-v5e6-r7y8-9012-abcdef345678",
"event_type": "email.received",
"event_id": "evt_abc123",
"status_code": 200,
"error": null,
"attempt": 1,
"duration_ms": 142,
"created_at": "2025-01-15T10:30:01Z"
},
{
"id": "d2e3l4i5-v6e7-r8y9-0123-bcdef4567890",
"event_type": "email.received",
"event_id": "evt_def456",
"status_code": 0,
"error": "connection timeout",
"attempt": 3,
"duration_ms": 30000,
"created_at": "2025-01-15T11:00:05Z"
}
]
}| Field | Type | Description |
|---|---|---|
event_type | string | Event that triggered the delivery |
event_id | string | Unique event identifier |
status_code | integer | HTTP response code (0 if connection failed) |
error | string? | Error message (null if successful) |
attempt | integer | Attempt number (1 = first try) |
duration_ms | integer | Request duration in milliseconds |