Your server

Receive events with a webhook

Your server gets a request for each message and each Case opened or resolved.

Updated October 28, 2026

The webhook sends a JSON POST request to your server for each event of the App. For example, to send a push notification from your server to a Contact who left the app: see Send push notifications.

Set it up

  1. Open App settings > Developers > Webhook.
  2. Enter your server’s https address, then save.
  3. Copy the signing secret that appears and keep it on your server.
  4. While you develop, receive the events on your computer with the Zean CLI. See Receive webhooks on your computer.

Events

EventDataDescription
message.contactdata.messageThe Contact wrote.
message.memberdata.message, data.pushA Member replied to the Contact. Internal notes and automatic AI replies send no event.
case.openeddata.caseIdA Case opens: on the first message, or when the conversation reopens.
case.resolveddata.caseIdA Case is resolved, by a Member or automatically.

The request

  • Method POST, with the Content-Type: application/json header.
  • Zean-Event header: the event type.
  • Zean-Signature header: the signature, to check before anything else. See Verify a webhook signature.

The body, for message.contact:

JSON
{  "id": "3f1c9a52-6a0e-4c43-9a51-2d1f0e8b7c11",  "type": "message.contact",  "test": false,  "createdAt": 1760515200000,  "appId": "…",  "data": {    "conversationId": "…",    "contact": {      "id": "…",      "externalId": "42",      "name": "Awa Diallo",      "email": "awa.diallo@example.com"    },    "message": {      "id": "…",      "body": "Hi, has my order shipped?",      "attachments": [        {          "name": "receipt.jpg",          "type": "image/jpeg",          "size": 184320        }      ],      "createdAt": 1760515200000    }  }}
FieldTypeDescription
idstringThe event’s ID. It stays the same from one try to the next: use it to skip a duplicate.
typestringThe type, as in the Zean-Event header.
testbooleantrue for a test event, sent only to the Zean CLI, never to the webhook address. false otherwise.
createdAtnumberWhen the event happened, in Unix milliseconds.
appIdstringThe App it concerns.
data.conversationIdstringThe Contact’s conversation.
data.contactobject | nullid, then externalId (a Verified Contact’s sub), name and email, null when unknown. null if the Contact no longer exists.
data.messageobjectFor message.contact and message.member: id, body, attachments (each file’s name, type and size; the files stay private), createdAt, and memberId for message.member.
data.pushobjectFor message.member: the push notification ready to send to the Contact. See below.
data.caseIdstringFor case.opened and case.resolved, in place of data.message.

A Member’s reply and its push notification

Only a Member’s reply sends message.member. Its body carries data.push, the notification ready to send to each of the Contact’s devices:

JSON
{  "id": "8b2e4f10-3c7d-4a9e-b5f1-6d0a2c9e7b34",  "type": "message.member",  "test": false,  "createdAt": 1760515260000,  "appId": "…",  "data": {    "conversationId": "…",    "contact": {      "id": "…",      "externalId": "42",      "name": "Awa Diallo",      "email": "awa.diallo@example.com"    },    "message": {      "id": "…",      "body": "Your order left the warehouse this morning.",      "attachments": [],      "memberId": "…",      "createdAt": 1760515260000    },    "push": {      "title": "Kibo Shop",      "body": "Your order left the warehouse this morning.",      "language": "en",      "tokens": ["ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"],      "data": { "zean": "message", "appId": "…", "conversationId": "…" }    }  }}
FieldTypeDescription
push.titlestringThe App’s name.
push.bodystringThe reply, cut to 150 characters with “…”. For files only: “Sent a photo”, “Sent 3 photos”, “Sent a file” or “Sent 3 files”, or “A envoyé une photo”, “A envoyé 3 photos”, “A envoyé un fichier” or “A envoyé 3 fichiers” for a Contact who speaks French.
push.languagestringThe Contact’s language, such as fr or pt-BR. A language starting with fr gives a French body.
push.tokensstring[]The push token of each of the Contact’s devices that gave one with setPushToken(): Expo, FCM or APNs. Empty when there is none.
push.dataobjectTo attach to the notification as its data. The SDK recognises it on a tap and opens the chat. See Send push notifications.

Delivery

  • The address must be public and use https: localhost and private networks are refused. On your computer, use the Zean CLI.
  • Each event goes to the webhook address and to every running Zean CLI.
  • Answer with a 2xx code within 10 seconds. Do long work after answering.
  • Otherwise, the call to the address is tried again up to 5 times: after 1 minute, 5 minutes, 30 minutes, 2 hours, then 6 hours.
  • Each try carries a fresh signature, but the same id.

Recent deliveries

App settings > Developers > Webhook > “Recent deliveries” shows the App’s last 50 calls, test events and “Send again” included. For each:

  • the event, and where it went: “Webhook address” or “Zean CLI”;
  • your server’s status code, or the error: timed out, connection refused, address not found, certificate error, no answer;
  • the start of your server’s reply, the time it took, when it was sent and the attempt number;
  • the “Test” badge for a test event.

“Send again” sends the same event once more, with the same id and the next attempt number. A successful send again to the address stops that event’s automatic retries.

Test events

A test event goes only to the running Zean CLIs, never to the webhook address: your production server never gets a test, and no real Contact is notified. Its test field is true.

  1. Run the Zean CLI on your computer. See Receive webhooks on your computer.
  2. Open the chat from a development build of your app or site: Expo Go, a React Native build in __DEV__, or a site on localhost. For 24 hours, this device can be “Your test device”.
  3. In App settings > Developers > Webhook > “Test events”, pick the event, then “Send a test event”. The button works while a Zean CLI runs.
  • The event carries your test device’s Contact. Without a test device, a sample Contact, with externalId set to "user_123".
  • For message.member, once your test device has written, Zean writes a real test reply in its Conversation: the push notification, the badge and the tap that opens the chat are real. It is marked “Test reply” in the inbox; no AI reads it and the stats do not count it.
  • The result shows in “Recent deliveries”. A test event is never retried.

Every event is always sent: ignore the ones you do not need. Leave the address empty to receive nothing at the address.