# Webhooks

DoneTalk tells your system what happens, with a signed POST to an address of yours: a campaign started or finished, a message was delivered or read, a contact replied, a conversation was closed with its outcome.

## Register an address

In **Settings › Integrations › Outgoing webhooks**, or through the API with the `webhooks:manage` scope:

```bash
curl -X POST https://api.donetalk.com/v1/c/{company}/webhooks \
  -H "Authorization: Bearer $DONETALK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-system.com/donetalk/events", "events": ["campaign.completed", "campaign.recipient.replied", "conversation.closed"]}'
```

The answer carries the `secret` only once: keep it to check the signatures.

## What arrives

```http
POST /donetalk/events HTTP/1.1
Content-Type: application/json
User-Agent: DoneTalk-Webhooks/1.0
DoneTalk-Signature: t=1792756800,v1=5f0c…
X-DoneTalk-Event: conversation.closed
X-DoneTalk-Delivery: 01a3e4f5-a6b7-7c8d-9e0f-1a2b3c4d5e6f
```

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.closed",
  "occurredAt": "2026-10-24T12:20:41.000Z",
  "customerId": "01a0c0de-…",
  "data": { "...": "the event's content" }
}
```

Events from a test key carry `"test": true`.

## Check the signature

`DoneTalk-Signature` carries `t` (when it was sent, in seconds) and `v1`, the HMAC SHA-256 of `"<t>.<exact body>"` with your `secret`. Refuse events with a `t` older than 5 minutes: then no one can replay an old event.

```js
import crypto from "node:crypto";

function validEvent(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
```

```python
import hmac, hashlib, time

def valid_event(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
```

The older `x-donetalk-signature` header (`sha256=` + HMAC of the body, without the time) goes along until 2027-01-21.

## Answering and retries

Answer `2xx` within 10 seconds; do heavy work afterwards. Without a `2xx`, DoneTalk retries after 1 min, 5 min, 30 min, 2 h and 12 h. After 20 failures in a row the address shows as "Failing" (it keeps receiving). The same event may arrive more than once: use its `id` to process it once. Arrival order is not guaranteed: use `occurredAt`.

## Events

| Event | When it arrives |
|---|---|
| `conversation.opened` | A conversation started (the contact wrote or clicked an ad). |
| `conversation.closed` | A conversation was closed, with the outcome, who closed it (person, AI assistant or automatic rule), the closing note, the contact, team, tags, how long it took and the campaign it came from. |
| `conversation.assigned` | A conversation was assigned to a person. |
| `conversation.transferred` | A conversation moved to another team or person. |
| `conversation.reopened` | A closed conversation was reopened. |
| `contact.updated` | A contact's data changed. |
| `contact.consent_changed` | The contact opted in or out of a purpose (marketing, for example). |
| `message.received` | The contact sent a message. |
| `message.sent` | A message of ours left for WhatsApp. |
| `message.delivered` | A message reached the contact's phone. |
| `message.read` | The contact read a message. |
| `message.failed` | A message could not be delivered; `error.code` is Meta's code. |
| `flow.event` | A flow reached a “Record event” block, with the name and data the block sets. |
| `appointment.booked` | An appointment was booked. |
| `appointment.rescheduled` | An appointment was moved. |
| `appointment.cancelled` | An appointment was cancelled. |
| `appointment.confirmed` | The contact confirmed the appointment. |
| `appointment.attended` | The contact attended. |
| `appointment.no_show` | The contact did not show up. |
| `campaign.started` | The campaign started sending; its list was frozen. |
| `campaign.paused` | The campaign paused: by a person, the API or a rule (template paused, low quality, many opt-outs, budget). |
| `campaign.resumed` | The campaign resumed sending. |
| `campaign.completed` | The campaign finished sending, with its totals. |
| `campaign.cancelled` | The campaign was cancelled. |
| `campaign.recipient.sent` | The campaign's message left for a contact. |
| `campaign.recipient.delivered` | The campaign's message reached the contact. |
| `campaign.recipient.read` | The contact read the campaign's message. |
| `campaign.recipient.replied` | The contact answered the campaign (the first answer), by text or tapping a button. |
| `campaign.recipient.failed` | The campaign's message failed for a contact; `errorCode` is Meta's code. |
| `campaign.recipient.opted_out` | The contact asked for no more campaigns (the “Stop promotions” button or a refusal at Meta). |

## Example of each event

### conversation.opened

A conversation started (the contact wrote or clicked an ad).

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.opened",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "handler": "flow",
    "source": "inbound"
  }
}
```

### conversation.closed

A conversation was closed, with the outcome, who closed it (person, AI assistant or automatic rule), the closing note, the contact, team, tags, how long it took and the campaign it came from.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.closed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "teamId": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
    "assigneeId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "outcome": "converted",
    "isSuccess": true,
    "closedBy": {
      "kind": "member",
      "id": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
      "name": "Tayomara Batista"
    },
    "actorKind": "member",
    "openedAt": "2026-10-24T12:00:04.000Z",
    "firstResponseAt": "2026-10-24T12:05:10.000Z",
    "messagesIn": 6,
    "messagesOut": 7,
    "outcomeLabel": {
      "pt-BR": "Venda",
      "en": "Sale",
      "es": "Venta"
    },
    "outcomeCategory": "success",
    "note": "Fechou o plano anual com o cupom ANA10.",
    "contact": {
      "name": "Ana Souza",
      "phone": "5548999990001"
    },
    "team": {
      "id": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
      "name": "Vendas"
    },
    "tags": [
      {
        "id": "01a12150-3610-7c00-b680-188c2b4c7313",
        "name": {
          "pt-BR": "VIP",
          "en": "VIP",
          "es": "VIP"
        }
      }
    ],
    "durationSeconds": 1237,
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b"
  }
}
```

### conversation.assigned

A conversation was assigned to a person.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.assigned",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "assigneeId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "teamId": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
    "reason": "distribution",
    "previousAssigneeId": null,
    "actorKind": "system"
  }
}
```

### conversation.transferred

A conversation moved to another team or person.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.transferred",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "teamId": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
    "assigneeId": null,
    "previousTeamId": null,
    "previousAssigneeId": null,
    "previousHandler": "flow",
    "actorKind": "flow",
    "reason": "Gestor de conta: Tayomara Batista"
  }
}
```

### conversation.reopened

A closed conversation was reopened.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.reopened",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677"
  }
}
```

### contact.updated

A contact's data changed.

```json
{
  "id": "01a3e4f5-…",
  "type": "contact.updated",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "changes": [
      "fields",
      "displayName"
    ]
  }
}
```

### contact.consent_changed

The contact opted in or out of a purpose (marketing, for example).

```json
{
  "id": "01a3e4f5-…",
  "type": "contact.consent_changed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channel": "whatsapp",
    "purpose": "marketing",
    "status": "opted_out",
    "source": "keyword",
    "occurredAt": "2026-10-24T12:06:00.000Z"
  }
}
```

### message.received

The contact sent a message.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.received",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "type": "text"
  }
}
```

### message.sent

A message of ours left for WhatsApp.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.sent",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "providerMessageId": "wamid.HBgMNTU0ODk5OTk5MDAwMRUCABEYEjE="
  }
}
```

### message.delivered

A message reached the contact's phone.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.delivered",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "delivered",
    "occurredAt": "2026-10-24T12:00:06.000Z"
  }
}
```

### message.read

The contact read a message.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.read",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "read",
    "occurredAt": "2026-10-24T12:03:51.000Z"
  }
}
```

### message.failed

A message could not be delivered; `error.code` is Meta's code.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.failed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "failed",
    "occurredAt": "2026-10-24T12:00:06.000Z",
    "error": {
      "code": "131026",
      "detail": "Message undeliverable"
    }
  }
}
```

### flow.event

A flow reached a “Record event” block, with the name and data the block sets.

```json
{
  "id": "01a3e4f5-…",
  "type": "flow.event",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "name": "lead.qualified",
    "data": {
      "plano": "anual",
      "origem": "site"
    }
  }
}
```

### appointment.booked

An appointment was booked.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.booked",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "booked",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.rescheduled

An appointment was moved.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.rescheduled",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "booked",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 1,
    "previousStartsAt": "2026-10-27T12:00:00.000Z",
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.cancelled

An appointment was cancelled.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.cancelled",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "cancelled",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": "2026-10-26T18:00:00.000Z",
    "cancelledByKind": "contact",
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.confirmed

The contact confirmed the appointment.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.confirmed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "confirmed",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": "2026-10-26T14:00:00.000Z",
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.attended

The contact attended.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.attended",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "attended",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.no_show

The contact did not show up.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.no_show",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "no_show",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### campaign.started

The campaign started sending; its list was frozen.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.started",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "running",
    "audience": 1240,
    "skipped": 24,
    "startedAt": "2026-10-24T12:00:00.000Z"
  }
}
```

### campaign.paused

The campaign paused: by a person, the API or a rule (template paused, low quality, many opt-outs, budget).

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.paused",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "paused",
    "reason": "optout_rate",
    "by": "system"
  }
}
```

### campaign.resumed

The campaign resumed sending.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.resumed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "running",
    "by": "api_key"
  }
}
```

### campaign.completed

The campaign finished sending, with its totals.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.completed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "completed",
    "totals": {
      "audience": 1216,
      "sent": 1216,
      "delivered": 1198,
      "read": 874,
      "replied": 141,
      "failed": 18,
      "skipped": 24,
      "optedOut": 6
    }
  }
}
```

### campaign.cancelled

The campaign was cancelled.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.cancelled",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "cancelled",
    "by": "member"
  }
}
```

### campaign.recipient.sent

The campaign's message left for a contact.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.sent",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "at": "2026-10-24T12:00:04.000Z"
  }
}
```

### campaign.recipient.delivered

The campaign's message reached the contact.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.delivered",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "at": "2026-10-24T12:00:06.000Z"
  }
}
```

### campaign.recipient.read

The contact read the campaign's message.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.read",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "at": "2026-10-24T12:03:51.000Z"
  }
}
```

### campaign.recipient.replied

The contact answered the campaign (the first answer), by text or tapping a button.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.replied",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "buttonIndex": 0,
    "text": "Quero o cupom"
  }
}
```

### campaign.recipient.failed

The campaign's message failed for a contact; `errorCode` is Meta's code.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.failed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "errorCode": "131026"
  }
}
```

### campaign.recipient.opted_out

The contact asked for no more campaigns (the “Stop promotions” button or a refusal at Meta).

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.opted_out",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "via": "button"
  }
}
```
