Events
Events reference
Every event shares one envelope. Subscribe per endpoint to the types you need; see Webhooks for signatures and retries.
Envelope#
{
"id": "evt_2kQ9x…",
"type": "message.received",
"apiVersion": "2026-10-01",
"created": 1790000000,
"appId": "cmg…",
"data": {}
}id is unique per event and stays the same across retries and replays — dedupe on it. created is Unix seconds.
message.received#
A user sent your app a message. A user's first message is also their opt-in (you'll get consent.updated with source user_initiated).
{
"id": "cmg4r7k2d0003ab12cd34ef56",
"from": "u_4f9XkQ2bT7mN1pR8sV0wYz",
"type": "text",
"text": "Where is my order?",
"media": [],
"context": {
"messageId": "cmg4r2x0p0001ab12cd34ef56"
},
"timestamp": "2026-10-01T09:31:02.000Z"
}| Field | Type | Notes |
|---|---|---|
| idrequired | string | |
| fromrequired | string | Per-app user id, e.g. u_4f9XkQ2bT7mN1pR8sV0wYz · pattern ^u_[0-9A-Za-z]{22}$ |
| typerequired | string | |
| textrequired | string | null | |
| mediarequired | object[] | |
| media[].idrequired | string | GET /media/:id |
| media[].mimeTyperequired | string | null | |
| media[].fileNamerequired | string | |
| media[].sizerequired | integer | |
| context | object | Present when the user replied to a message |
| context.messageIdrequired | string | |
| timestamprequired | string (date-time) |
message.status#
A message you sent changed state: sent, delivered (reached a device), read, or failed.
{
"id": "cmg4r2x0p0001ab12cd34ef56",
"appUserId": "u_4f9XkQ2bT7mN1pR8sV0wYz",
"status": "read",
"timestamp": "2026-10-01T09:30:41.000Z",
"clientReference": "order-1042"
}| Field | Type | Notes |
|---|---|---|
| idrequired | string | |
| appUserIdrequired | string | Per-app user id, e.g. u_4f9XkQ2bT7mN1pR8sV0wYz · pattern ^u_[0-9A-Za-z]{22}$ |
| statusrequired | "sent" | "delivered" | "read" | "failed" | |
| timestamprequired | string (date-time) | |
| clientReference | string |
consent.updated#
A user opted in, stopped your messages, or blocked you. On opted_out or blocked, stop messaging them — sends will fail with consent_required. reason account_deleted means the account is gone.
{
"appUserId": "u_4f9XkQ2bT7mN1pR8sV0wYz",
"status": "opted_in",
"source": "opt_in_link",
"ref": "store-42"
}| Field | Type | Notes |
|---|---|---|
| appUserIdrequired | string | Per-app user id, e.g. u_4f9XkQ2bT7mN1pR8sV0wYz · pattern ^u_[0-9A-Za-z]{22}$ |
| statusrequired | "opted_in" | "opted_out" | "blocked" | |
| source | "user_initiated" | "profile_allow" | "opt_in_link" | "sandbox_tester" | |
| ref | string | The opt-in link ref, for source opt_in_link |
| reason | "account_deleted" |
app.status_changed#
Your app moved on the access ladder: approved, suspended, reinstated, or dropped to internal_live when verification is revoked.
{
"status": "internal_live",
"previousStatus": "sandbox"
}| Field | Type | Notes |
|---|---|---|
| statusrequired | string | |
| previousStatusrequired | string | |
| reason | string |
webhook.test#
Sent only by Send test event, to that endpoint. Data: { message, subscriptionId, nonce }.