MOLTED EMAIL

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

CLI (recommended)
molted webhooks create --url https://example.com/hooks/molted --events inbound.received,delivery.bounced
curl (via agentic mailbox API)
curl -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

FieldTypeRequiredDescription
urlstringYesThe HTTPS URL to receive events.
eventsstring[]NoEvent types to subscribe to. Omit or pass [] to receive all events.
descriptionstringNoA label for this endpoint.
authType'hmac' | 'bearer' | 'both'NoAuthentication mode. Defaults to hmac. See Authentication.
bearerTokenstringConditionalRequired when authType is bearer or both. Sent as Authorization: Bearer <token>.

Response

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:

authTypeHeaders sentWhen to use
hmac (default)X-Molted-SignatureRecommended for most cases. Verifies the request came from Molted and the payload wasn't tampered with.
bearerAuthorization: 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.
bothBoth headersDefense in depth — your endpoint can check bearer at the edge and HMAC in the handler.
CLI — create with bearer auth
molted webhooks create \
  --url https://example.com/hooks/molted \
  --auth-type bearer \
  --bearer-token "tok_a1b2c3..."
curl — create with bearer auth
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

CLI
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:

CLI
molted webhooks update --id ENDPOINT_ID --auth-type hmac --bearer-token null

Verifying bearer auth on your endpoint

Node.js / Express
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

EventWhen it fires
inbound.receivedNew inbound email stored
inbound.classifiedInbound email classified by AI
inbound.routedInbound email routed to a thread
delivery.sentEmail sent to provider
delivery.deliveredEmail confirmed delivered
delivery.bouncedEmail bounced (triggers auto-suppression)
delivery.complainedSpam complaint received (triggers auto-suppression)

Payload format

Events are sent as HTTP POST requests with the following headers:

HeaderSent whenDescription
Content-TypeAlwaysapplication/json
X-Molted-EventAlwaysEvent type (e.g. inbound.received)
X-Molted-DeliveryAlwaysUnique delivery ID
X-Molted-SignatureauthType is hmac or bothHMAC-SHA256 signature of the raw body
AuthorizationauthType is bearer or bothBearer <your token>
Payload body
{
  "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:

Node.js
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

CLI
molted webhooks list

Get endpoint details

CLI
molted webhooks get --id ENDPOINT_ID

Returns the full endpoint object including secret.

Update an endpoint

CLI
molted webhooks update --id ENDPOINT_ID --enabled false

Updatable flags: --url, --events, --enabled, --description, --auth-type, --bearer-token.

Delete an endpoint

CLI
molted webhooks delete --id ENDPOINT_ID

View delivery history

CLI
molted webhooks deliveries --id ENDPOINT_ID

Returns the 50 most recent deliveries with status (pending, delivered, failed), HTTP status code, and attempt count.

Send a test event

CLI
molted webhooks test --id ENDPOINT_ID

Sends a test event to your endpoint URL and returns the result:

Success
{
  "success": true,
  "httpStatus": 200,
  "deliveryId": "..."
}

Local development

Use molted webhooks listen to receive webhook events on your local machine during development:

CLI
molted webhooks listen --port 9876 --events inbound.received

This 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).