openmail sends webhooks to your configured URL when inbound email is received. Use webhooks to power real-time features like live chat, ticket creation, or AI assistants.
Events
| Event | Description |
|---|
message.received | A new inbound email was delivered to an inbox |
Payload structure
{
"event": "message.received",
"event_id": "evt_abc123",
"occurred_at": "2024-03-21T10:05:00.000Z",
"delivered_at": "2024-03-21T10:05:01.000Z",
"attempt": 1,
"inbox_id": "inb_92ma...",
"external_id": "user_abc123",
"thread_id": "thr_xyz...",
"message": {
"id": "msg_...",
"rfc_message_id": "<original@message.id>",
"from": "sender@example.com",
"to": "inbox@yourdomain.openmail.sh",
"cc": [],
"subject": "Email subject",
"body_text": "Plain text body",
"attachments": [
{
"filename": "document.pdf",
"contentType": "application/pdf",
"sizeBytes": 12345,
"url": "https://api.openmail.sh/v1/attachments/msg_.../document.pdf"
}
],
"received_at": "2024-03-21T10:05:00.000Z"
}
}
| Header | Description |
|---|
Content-Type | application/json |
X-Timestamp | Unix timestamp (seconds) used in signature |
X-Signature | HMAC-SHA256 signature (see below) |
X-Event-Id | Unique event ID for deduplication |
Signature verification
The signature is computed as:
HMAC-SHA256(webhook_secret, "{timestamp}.{raw_json_payload}")
Verify before processing:
- Read the raw request body as a string (do not parse JSON first).
- Get
X-Timestamp and X-Signature from headers.
- Compute
HMAC-SHA256(secret, timestamp + "." + body).
- Compare with
X-Signature using a constant-time comparison.
Use constant-time comparison (e.g. crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing attacks.
Best practices
- Respond quickly — Return
200 within 15 seconds. Process asynchronously if needed.
- Idempotency — Use
event_id to deduplicate. We may retry; your handler should be idempotent.
- Attachment URLs — Fetch promptly; signed URLs expire.
- Verify signatures — Never process webhooks without verifying the signature.
Retries
Failed deliveries (non-2xx or timeout) are retried up to 5 times with exponential backoff (~30s, 60s, 120s, 240s, 480s).