Webhooks
Receive real-time notifications when events occur in your Molted Email account.
Webhooks let you receive HTTP POST notifications when events happen in your account -- like an email being delivered, bounced, or a new inbound message arriving. You register an endpoint URL, and Molted Email sends events to it in real time.
Webhook endpoints are managed via /v1/me/webhooks. This requires session cookie auth when called directly, but is accessible with Bearer token auth through the agentic mailbox API proxy.
Registering a webhook
molted webhooks create --url https://example.com/hooks/molted --events inbound.received,delivery.bouncedcurl -X POST https://agentic-mailbox.molted.email/v1/me/webhooks?tenantId=TENANT_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/molted",
"events": ["inbound.received", "delivery.bounced"],
"description": "Production webhook"
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The HTTPS URL to receive events. |
events | string[] | No | Event types to subscribe to. Omit or pass [] to receive all events. |
description | string | No | A label for this endpoint. |
authType | 'hmac' | 'bearer' | 'both' | No | Authentication mode. Defaults to hmac. See Authentication. |
bearerToken | string | Conditional | Required when authType is bearer or both. Sent as Authorization: Bearer <token>. |
Response
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.com/hooks/molted",
"secret": "whsec_a1b2c3d4e5f6...",
"events": ["inbound.received", "delivery.bounced"],
"enabled": true,
"description": "Production webhook",
"authType": "hmac",
"bearerTokenSet": false,
"createdAt": "2026-03-01T12:00:00Z",
"updatedAt": "2026-03-01T12:00:00Z"
}Save the secret -- you will need it to verify signatures. It is only returned on create.
The bearerToken you supply is write-only -- subsequent GET and LIST calls return bearerTokenSet: true but never the plaintext token. To rotate, send a new value via PATCH.
Authentication
Choose how Molted Email authenticates webhook deliveries to your endpoint:
authType | Headers sent | When to use |
|---|---|---|
hmac (default) | X-Molted-Signature | Recommended for most cases. Verifies the request came from Molted and the payload wasn't tampered with. |
bearer | Authorization: Bearer <token> | Your endpoint already validates a static bearer token (e.g. behind an API gateway). Simpler to verify, but only proves the sender — not payload integrity. |
both | Both headers | Defense in depth — your endpoint can check bearer at the edge and HMAC in the handler. |
molted webhooks create \
--url https://example.com/hooks/molted \
--auth-type bearer \
--bearer-token "tok_a1b2c3..."curl -X POST https://agentic-mailbox.molted.email/v1/agent/webhooks?tenantId=TENANT_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/molted",
"authType": "bearer",
"bearerToken": "tok_a1b2c3..."
}'Bearer endpoints must use https:// URLs. Plaintext tokens in Authorization headers over HTTP would be trivially sniffable.
Rotating a bearer token
molted webhooks update --id ENDPOINT_ID --bearer-token "tok_new_value"To clear a bearer token (when switching back to hmac only), pass the literal string null:
molted webhooks update --id ENDPOINT_ID --auth-type hmac --bearer-token nullVerifying bearer auth on your endpoint
app.post('/hooks/molted', (req, res) => {
const auth = req.header('Authorization');
const expected = `Bearer ${process.env.MOLTED_WEBHOOK_TOKEN}`;
// Use a constant-time comparison to avoid timing attacks
if (!auth || !crypto.timingSafeEqual(Buffer.from(auth), Buffer.from(expected))) {
return res.status(401).end();
}
// ... process event
res.status(200).end();
});Event types
| Event | When it fires |
|---|---|
inbound.received | New inbound email stored |
inbound.classified | Inbound email classified by AI |
inbound.routed | Inbound email routed to a thread |
delivery.sent | Email sent to provider |
delivery.delivered | Email confirmed delivered |
delivery.bounced | Email bounced (triggers auto-suppression) |
delivery.complained | Spam complaint received (triggers auto-suppression) |
Payload format
Events are sent as HTTP POST requests with the following headers:
| Header | Sent when | Description |
|---|---|---|
Content-Type | Always | application/json |
X-Molted-Event | Always | Event type (e.g. inbound.received) |
X-Molted-Delivery | Always | Unique delivery ID |
X-Molted-Signature | authType is hmac or both | HMAC-SHA256 signature of the raw body |
Authorization | authType is bearer or both | Bearer <your token> |
{
"event": "inbound.received",
"timestamp": "2026-03-22T10:00:00Z",
"data": {
"messageId": "...",
"from": "sender@example.com",
"subject": "Hello"
}
}Verifying signatures
Every webhook payload is signed with your endpoint's secret using HMAC-SHA256. Always verify the signature before processing:
const crypto = require('crypto');
function verifyWebhook(rawBody, signatureHeader, secret) {
const signature = crypto
.createHmac('sha256', secret)
.update(rawBody) // must be raw bytes, not parsed JSON
.digest('hex');
const expected = signatureHeader.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Retries and delivery
- Timeout: 5 seconds per attempt
- Max attempts: 5
- Backoff: Exponential (10s, 20s, 40s, 80s, 160s)
- Success: HTTP 2xx response
- Failure: After 5 failed attempts, the delivery is marked as
failed
Managing endpoints
List endpoints
molted webhooks listGet endpoint details
molted webhooks get --id ENDPOINT_IDReturns the full endpoint object including secret.
Update an endpoint
molted webhooks update --id ENDPOINT_ID --enabled falseUpdatable flags: --url, --events, --enabled, --description, --auth-type, --bearer-token.
Delete an endpoint
molted webhooks delete --id ENDPOINT_IDView delivery history
molted webhooks deliveries --id ENDPOINT_IDReturns the 50 most recent deliveries with status (pending, delivered, failed), HTTP status code, and attempt count.
Send a test event
molted webhooks test --id ENDPOINT_IDSends a test event to your endpoint URL and returns the result:
{
"success": true,
"httpStatus": 200,
"deliveryId": "..."
}Local development
Use molted webhooks listen to receive webhook events on your local machine during development:
molted webhooks listen --port 9876 --events inbound.receivedThis starts a local HTTP server, registers a temporary webhook endpoint pointing to it, and prints incoming events to stdout in formatted JSON. The temporary endpoint is automatically cleaned up when you stop listening (Ctrl-C).