Webhooks
Receive real-time HTTP notifications when events occur in your account.
Overview
Webhooks allow your application to receive real-time POST requests whenever specific events happen in your mailservr account, such as receiving an email or creating a new thread.
Webhooks are available on the Enterprise plan. You can register up to 5 webhook endpoints from Settings.
Events
email.received
Fired when an inbound email is received by one of your aliases.
{
"event": "email.received",
"data": {
"emailId": 142,
"threadId": 87,
"from": {
"address": "[email protected]",
"name": "Jane Doe"
},
"to": ["[email protected]"],
"subject": "Meeting tomorrow",
"snippet": "Hey, are we still on for...",
"receivedAt": "2026-02-13T14:30:00.000Z"
}
}email.sent
Fired when you send an email (compose or reply).
{
"event": "email.sent",
"data": {
"emailId": 143,
"threadId": 87,
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Re: Meeting tomorrow",
"sentAt": "2026-02-13T15:00:00.000Z"
}
}thread.created
Fired when a new email thread is created (from an inbound email or a new compose).
{
"event": "thread.created",
"data": {
"threadId": 87,
"subject": "Meeting tomorrow",
"addressId": 12,
"createdAt": "2026-02-13T14:30:00.000Z"
}
}Request Format
Each webhook delivery is an HTTPS POST request with a JSON body. The following headers are included:
| Header | Description |
|---|---|
| Content-Type | Always application/json |
| X-Webhook-Signature | HMAC-SHA256 hex digest for verifying authenticity |
| X-Webhook-Timestamp | Unix timestamp (seconds) when the request was signed |
| User-Agent | mailservr-webhooks/1.0 |
Verifying Signatures
Every webhook request is signed with the secret shown when you created the endpoint. You should always verify the signature to ensure the request is authentic and hasn't been tampered with.
Algorithm
- Extract the
X-Webhook-TimestampandX-Webhook-Signatureheaders - Concatenate the timestamp, a period, and the raw request body:
`${timestamp}.${body}` - Compute HMAC-SHA256 of that string using your signing secret
- Compare the hex digest to the signature header (use a timing-safe comparison)
- Optionally reject requests where the timestamp is older than 5 minutes to prevent replay attacks
Node.js
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhook(body, signature, timestamp, secret) {
const signedPayload = `${timestamp}.${body}`;
const expected = createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
const sigBuf = Buffer.from(signature, "utf8");
const expBuf = Buffer.from(expected, "utf8");
if (sigBuf.length !== expBuf.length) return false;
return timingSafeEqual(sigBuf, expBuf);
}Python
import hmac
import hashlib
def verify_webhook(body: str, signature: str, timestamp: str, secret: str) -> bool:
signed_payload = f"{timestamp}.{body}"
expected = hmac.new(
secret.encode(), signed_payload.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func VerifyWebhook(body, signature, timestamp, secret string) bool {
signedPayload := fmt.Sprintf("%s.%s", timestamp, body)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(signedPayload))
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}Best Practices
- Always verify signatures. Never trust a webhook request without checking the HMAC signature against your signing secret.
- Respond quickly. Return a 2xx status code within 10 seconds. Do heavy processing asynchronously after responding.
- Use HTTPS. Webhook endpoints must use HTTPS. HTTP URLs are rejected.
- Handle duplicates. In rare cases the same event may be delivered more than once. Use the
emailIdorthreadIdto deduplicate. - Check the timestamp. Reject requests where the
X-Webhook-Timestampis older than 5 minutes to prevent replay attacks. - Keep your secret safe. The signing secret is shown only once when you create the endpoint. Store it securely. If compromised, delete the endpoint and create a new one.
Delivery Behavior
- Deliveries time out after 10 seconds.
- A delivery is considered successful if your endpoint returns a 2xx status code.
- Failed deliveries are logged and visible in your settings page. There are no automatic retries.
- Delivery logs are retained for 30 days.
- You can toggle endpoints on/off without deleting them.
Managing Webhooks
Webhook endpoints are managed from the Webhooks section in your Settings page. You can also manage them via the API:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/webhooks | List your webhook endpoints |
| POST | /api/webhooks | Create a new endpoint |
| PATCH | /api/webhooks/:id | Update events or toggle enabled |
| DELETE | /api/webhooks/:id | Delete an endpoint |
| GET | /api/webhooks/:id/deliveries | View delivery logs |
All endpoints require authentication via session cookie or Authorization: Bearer <api-key> header.