Webhooks
Be told, instead of asking again.
Webhooks tell a server what happens as it happens. An organisation's admins add HTTPS endpoints in Control › Webhooks and pick the events. Each delivery is a JSON POST, signed, retried for about 1.5 hours.
A delivery
{
"id": "cm31…",
"event": "approval.decided",
"createdAt": "2026-10-17T09:41:12.000Z",
"data": {
"approval": {
"id": "cm2x…",
"status": "approved",
"module": "salesInvoices",
"action": "create",
"recordId": "cm2w…"
}
}
}Check the signature#
ZiloMail-Signature: t=1760693472,v1=5257a8…: an HMAC-SHA256 of <t>.<body> with the endpoint's secret (shown when it was added). Compare in constant time and refuse old timestamps (5 minutes). ZiloMail-Event and ZiloMail-Delivery carry the event and the delivery's id (the same delivery retried keeps its id: use it to ignore repeats).
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const mac = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(mac), Buffer.from(parts.v1 ?? ""));
}Events#
| Event | When |
|---|---|
message.received | New mail stored in a mailbox (Inbox, a filter's folder or Junk). |
message.sent | Someone sent mail from webmail, a mail app or the API. |
delivery.succeeded | Another server accepted a message sent from here. |
delivery.failed | A message couldn't be delivered (bounced) and won't be retried. |
drive.file.uploaded | A file was uploaded to ZiloDrive (from Drive, ZiloPDF or the API) and passed the virus scan. |
drive.item.trashed | A file, folder, document, spreadsheet or presentation was moved to Trash. |
drive.item.shared | Something was shared with a colleague, or a public link was made. |
document.created | A new document, spreadsheet or presentation (kind: doc, sheet, slide). |
user.created | A person was added to the organisation (in Control or the API). |
user.disabled | A person's mailbox was turned off. |
user.enabled | A person's mailbox was turned back on. |
security.threat_blocked | ZiloSecur found a virus in mail or a ZiloDrive file and stopped it. |
security.password_attack | Many wrong passwords for one account in a short time (someone may be guessing). |
security.new_device | Someone signed in from a browser or device the account hasn't used before. |
app.signed_in | Someone signed in to one of your Sign in with ZiloOne apps. |
approval.requested | A change (from a page or the API) is waiting for an approver: invoices, customers, leave and every module with approval levels. |
approval.decided | A change waiting for approval was approved (and made) or rejected. |
Answer quickly#
Answer 2xx within 10 seconds and do the work afterwards. An endpoint that fails 20 deliveries in a row is switched off (the admin is told).