CherryMail
Skip to documentation
Developersv1
Search the CherryMail developer documentation
Developers/Integrations
Integrations

Receive signed webhooks

React to incoming messages and account events without repeatedly calling the API.

Create a subscription

Use Integrations or POST /webhooks/ with an owner or admin credential that has webhook permission. Email events also require read permission, selected mailboxes, and current mailbox grants. Select the exact events and mailbox IDs you need. Save the signing secret when it is shown.

Example request
curl -X POST 'https://cherrymail.net/api/v1/webhooks/' \
  -H "Authorization: Bearer $CHERRYMAIL_API_KEY" \
  -H "Idempotency-Key: example-request-1042" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://your-app.example/cherrymail/events",
  "events": [
    "message.received"
  ],
  "mailbox_ids": [
    123
  ]
}'

Destinations must use public HTTPS on port 443. Redirects, private addresses, and internal networks are refused. Destination DNS is revalidated when connecting.

Available events

EventPurpose
message.receivedMailbox activity
message.submittedMailbox activity
domain.verifiedResource or service status
mailbox.readyResource or service status
mailbox.deletedResource or service status
alias.readyResource or service status
alias.deletedResource or service status
storage.80Storage threshold
storage.100Storage threshold
service.suspendedResource or service status
service.readyResource or service status
webhook.testLabelled test delivery

Email events contain identifiers and minimal metadata. Retrieve message content and attachments through the authorized API.

Incoming messages start from a baseline
Subscribed INBOX folders are polled every 30 seconds. Existing messages are not announced. A UIDVALIDITY change establishes a new baseline. Messages removed before polling observes them cannot be reported.

Verify every delivery

Read the raw request body before parsing JSON. Compute HMAC-SHA256 over timestamp + "." + rawBody with your signing secret. Compare it to CherryMail-Signature, including its v1= prefix.

Verify the signature
import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, timestamp, signature, secret) {
  if (!/^\d+$/.test(timestamp) ||
      Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return false;
  }
  const expected = Buffer.from('v1=' + createHmac('sha256', secret)
    .update(timestamp + '.').update(rawBody).digest('hex'));
  const actual = Buffer.from(signature);
  return actual.length === expected.length &&
    timingSafeEqual(actual, expected);
}

Use the CherryMail-Timestamp header, reject timestamps outside a five-minute window, compare signatures in constant time, and deduplicate by the stable event ID.

Delivery, retries & replay

Return a 2xx response promptly, then process the event asynchronously. Delivery is at least once and ordering is not guaranteed. Up to ten automatic attempts use exponential backoff.

Inspect delivery history, send test events, and manually replay deliveries in Integrations. A replay preserves the original event ID, so your deduplication must also handle intentional replays. Revoking a credential or grant stops dependent deliveries.

Next upConnect an MCP client