# Event catalog

> Every webhook event BlueBot sends, when it fires, and a full example payload for each.

Every event BlueBot sends to a webhook, with a full example of each. Each one is posted as JSON with the same envelope: `id` (the event, `evt_…`, also sent as the `webhook-id` header, the same on every retry), `object: "event"`, `type`, `api_version` (the payload version your endpoint is pinned to, `2026-11-13` today), `timestamp`, and `data`. Message events carry the whole message as `GET /v1/messages/{id}` returns it, `metadata` included, so you can match an event to the request that caused it.

| Event                                                             | When it fires                                                                                                                        |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| [`message.queued`](https://usebluebot.com/docs/webhooks/events#messagequeued)           | A text was accepted and is waiting for the Mac, or a scheduled text became due.                                                      |
| [`message.sent`](https://usebluebot.com/docs/webhooks/events#messagesent)               | Messages on the Mac showed the text sent. Also fires for a text you typed by hand on your Mac or iPhone, with `source: "mac"`.       |
| [`message.delivered`](https://usebluebot.com/docs/webhooks/events#messagedelivered)     | iMessage showed the text delivered to the person’s device.                                                                           |
| [`message.read`](https://usebluebot.com/docs/webhooks/events#messageread)               | The person read the text. Only for people who share read receipts.                                                                   |
| [`message.failed`](https://usebluebot.com/docs/webhooks/events#messagefailed)           | Messages couldn’t send the text, or it was an iMessage-only text to somebody not on iMessage.                                        |
| [`message.unconfirmed`](https://usebluebot.com/docs/webhooks/events#messageunconfirmed) | The Mac took the text and didn’t say whether it went before its 3-minute lease ran out.                                              |
| [`message.expired`](https://usebluebot.com/docs/webhooks/events#messageexpired)         | The text was still waiting when its `expires_at` passed, so it will never go.                                                        |
| [`message.canceled`](https://usebluebot.com/docs/webhooks/events#messagecanceled)       | A queued or scheduled text was canceled: by you, because the person is on your stop list, or when its sequence or broadcast stopped. |
| [`message.received`](https://usebluebot.com/docs/webhooks/events#messagereceived)       | Somebody texted you. Pictures and voice memos are on the message as files.                                                           |
| [`message.edited`](https://usebluebot.com/docs/webhooks/events#messageedited)           | A text was edited in Messages, by you or by the other person.                                                                        |
| [`message.unsent`](https://usebluebot.com/docs/webhooks/events#messageunsent)           | A text was unsent in Messages. Its words are gone from BlueBot too.                                                                  |
| [`reaction.added`](https://usebluebot.com/docs/webhooks/events#reactionadded)           | Somebody added a tapback (love, like, laugh and so on) to a text, or you did from your Mac or iPhone.                                |
| [`reaction.removed`](https://usebluebot.com/docs/webhooks/events#reactionremoved)       | A tapback was taken back.                                                                                                            |
| [`chat.created`](https://usebluebot.com/docs/webhooks/events#chatcreated)               | The Mac saw a chat BlueBot didn’t know: a new group, or a first text with somebody.                                                  |
| [`contact.created`](https://usebluebot.com/docs/webhooks/events#contactcreated)         | A person was added: by a send to somebody new, a text from somebody new, an import, the API or the dashboard.                        |
| [`contact.stopped`](https://usebluebot.com/docs/webhooks/events#contactstopped)         | A person went on your stop list. Their queued texts were canceled.                                                                   |
| [`contact.unstopped`](https://usebluebot.com/docs/webhooks/events#contactunstopped)     | A person came off your stop list: they texted START, or you took them off.                                                           |
| [`sequence.completed`](https://usebluebot.com/docs/webhooks/events#sequencecompleted)   | A person reached the end of a sequence, or it stopped early because they replied or stopped.                                         |
| [`broadcast.completed`](https://usebluebot.com/docs/webhooks/events#broadcastcompleted) | Every text in a broadcast has settled: sent, failed, skipped, expired or canceled.                                                   |
| [`automation.matched`](https://usebluebot.com/docs/webhooks/events#automationmatched)   | An incoming text matched a keyword rule that has the “Send a webhook event” action.                                                  |
| [`device.online`](https://usebluebot.com/docs/webhooks/events#deviceonline)             | The Mac checked in after being offline. Held texts start going.                                                                      |
| [`device.offline`](https://usebluebot.com/docs/webhooks/events#deviceoffline)           | The Mac hasn’t checked in for 2 minutes. Texts wait with `hold.reason: "device_offline"`.                                            |
| [`device.check_failed`](https://usebluebot.com/docs/webhooks/events#devicecheck_failed) | One of the Mac’s setup checks turned `needs_you`, such as Messages signing out or Full Disk Access being turned off.                 |

Subscribe to the events you need when you make the webhook; an endpoint never receives types it didn’t ask for. To check a handler against these payloads without sending a text, use **Send a test event** in the dashboard, `POST /v1/webhooks/{id}/test`, or `bluebot listen`.

## Messages

### `message.queued`

A text was accepted and is waiting for the Mac, or a scheduled text became due.

Its `hold` says why it is waiting, if anything holds it. A queued text can still be canceled.

```json title="message.queued"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.queued",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "queued",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": null,
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": null,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": null
  }
}
```

### `message.sent`

Messages on the Mac showed the text sent. Also fires for a text you typed by hand on your Mac or iPhone, with `source: "mac"`.

`service` says whether it went as an iMessage or a regular text. A text sent with a file that couldn’t be attached is sent with `failure` set.

```json title="message.sent"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.sent",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "sent",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": "imessage",
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": "2026-11-13T19:41:02.000Z",
    "sent_at": "2026-11-13T19:41:05.000Z",
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": null
  }
}
```

### `message.delivered`

iMessage showed the text delivered to the person’s device.

Regular texts (SMS) never report delivery, so a text that went as SMS stays `sent`.

```json title="message.delivered"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.delivered",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "delivered",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": "imessage",
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": "2026-11-13T19:41:02.000Z",
    "sent_at": "2026-11-13T19:41:05.000Z",
    "delivered_at": "2026-11-13T19:41:06.000Z",
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": null
  }
}
```

### `message.read`

The person read the text. Only for people who share read receipts.

Most people don’t share read receipts, so don’t wait for this event.

```json title="message.read"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.read",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "read",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": "imessage",
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": "2026-11-13T19:41:02.000Z",
    "sent_at": "2026-11-13T19:41:05.000Z",
    "delivered_at": "2026-11-13T19:41:06.000Z",
    "read_at": "2026-11-13T19:43:30.000Z",
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": null
  }
}
```

### `message.failed`

Messages couldn’t send the text, or it was an iMessage-only text to somebody not on iMessage.

`failure.reason` is a stable code and `failure.detail` says what the Mac saw.

```json title="message.failed"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.failed",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "failed",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "imessage",
    "service": null,
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": {
      "reason": "not_on_imessage",
      "detail": "Messages couldn’t send this as an iMessage."
    },
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": "2026-11-13T19:41:02.000Z",
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": "2026-11-13T19:41:09.000Z",
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": null
  }
}
```

### `message.unconfirmed`

The Mac took the text and didn’t say whether it went before its 3-minute lease ran out.

It may have gone. Check the conversation, then settle it with `POST /v1/messages/{id}/resolve`: `mark_sent` if it went, `send_again` if not. BlueBot never sends it again on its own.

```json title="message.unconfirmed"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.unconfirmed",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "unconfirmed",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": null,
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": "2026-11-13T19:41:02.000Z",
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": "2026-11-13T19:44:02.000Z",
    "expired_at": null,
    "canceled_at": null,
    "received_at": null
  }
}
```

### `message.expired`

The text was still waiting when its `expires_at` passed, so it will never go.

Usually the Mac was away or the text sat behind quiet hours or a daily limit for longer than the expiry setting.

```json title="message.expired"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.expired",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "expired",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": null,
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": null,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": "2026-11-14T19:41:00.000Z",
    "canceled_at": null,
    "received_at": null
  }
}
```

### `message.canceled`

A queued or scheduled text was canceled: by you, because the person is on your stop list, or when its sequence or broadcast stopped.

`cancel_reason` says which.

```json title="message.canceled"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.canceled",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "message",
    "direction": "out",
    "state": "canceled",
    "to": "+15555550123",
    "from": "+15555550100",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Hi Maria, the showing at 412 Oak St is confirmed for 3 PM.",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": null,
    "sms_retry": false,
    "source": "api",
    "source_ref": null,
    "metadata": {
      "crm_id": "48213"
    },
    "hold": null,
    "send_at": null,
    "expires_at": "2026-11-14T19:41:00.000Z",
    "failure": null,
    "cancel_reason": "opted_out",
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:41:00.000Z",
    "queued_at": "2026-11-13T19:41:00.000Z",
    "sending_at": null,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": "2026-11-13T19:42:00.000Z",
    "received_at": null
  }
}
```

### `message.received`

Somebody texted you. Pictures and voice memos are on the message as files.

Fires once the Mac has uploaded the message’s files, or after 2 minutes without them (their `status` is then `waiting`). Group texts carry `chat_id`.

```json title="message.received"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.received",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_2Bf8QzD4kJmA6nXwEuTr1c",
    "object": "message",
    "direction": "in",
    "state": "received",
    "to": "+15555550100",
    "from": "+15555550123",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Perfect, see you there!",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": "imessage",
    "sms_retry": false,
    "source": "mac",
    "source_ref": null,
    "metadata": {},
    "hold": null,
    "send_at": null,
    "expires_at": null,
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": null,
    "created_at": "2026-11-13T19:44:12.000Z",
    "queued_at": null,
    "sending_at": null,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": "2026-11-13T19:44:10.000Z"
  }
}
```

### `message.edited`

A text was edited in Messages, by you or by the other person.

`text` is the new words and `edited_at` when. BlueBot can read edits but can’t make them.

```json title="message.edited"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.edited",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_2Bf8QzD4kJmA6nXwEuTr1c",
    "object": "message",
    "direction": "in",
    "state": "received",
    "to": "+15555550100",
    "from": "+15555550123",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": "Perfect, see you there at 3!",
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": "imessage",
    "sms_retry": false,
    "source": "mac",
    "source_ref": null,
    "metadata": {},
    "hold": null,
    "send_at": null,
    "expires_at": null,
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": "2026-11-13T19:44:40.000Z",
    "unsent_at": null,
    "created_at": "2026-11-13T19:44:12.000Z",
    "queued_at": null,
    "sending_at": null,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": "2026-11-13T19:44:10.000Z"
  }
}
```

### `message.unsent`

A text was unsent in Messages. Its words are gone from BlueBot too.

`text` is null and `unsent_at` says when.

```json title="message.unsent"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "message.unsent",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "msg_2Bf8QzD4kJmA6nXwEuTr1c",
    "object": "message",
    "direction": "in",
    "state": "received",
    "to": "+15555550100",
    "from": "+15555550123",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "text": null,
    "text_deleted": false,
    "files": [],
    "service_requested": "auto",
    "service": "imessage",
    "sms_retry": false,
    "source": "mac",
    "source_ref": null,
    "metadata": {},
    "hold": null,
    "send_at": null,
    "expires_at": null,
    "failure": null,
    "cancel_reason": null,
    "resolution": null,
    "resolved_at": null,
    "resent_message_id": null,
    "reply_to_id": null,
    "reactions": [],
    "edited_at": null,
    "unsent_at": "2026-11-13T19:45:00.000Z",
    "created_at": "2026-11-13T19:44:12.000Z",
    "queued_at": null,
    "sending_at": null,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null,
    "unconfirmed_at": null,
    "expired_at": null,
    "canceled_at": null,
    "received_at": "2026-11-13T19:44:10.000Z"
  }
}
```

## Reactions

### `reaction.added`

Somebody added a tapback (love, like, laugh and so on) to a text, or you did from your Mac or iPhone.

`direction` says whose it was. BlueBot can read tapbacks but can’t send them.

```json title="reaction.added"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "reaction.added",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "rxn_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "reaction",
    "kind": "love",
    "emoji": null,
    "from": "+15555550123",
    "direction": "in",
    "added_at": "2026-11-13T19:45:00.000Z",
    "message_id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "removed_at": null
  }
}
```

### `reaction.removed`

A tapback was taken back.

The reaction has the id `reaction.added` gave it.

```json title="reaction.removed"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "reaction.removed",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "rxn_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "reaction",
    "kind": "love",
    "emoji": null,
    "from": "+15555550123",
    "direction": "in",
    "added_at": "2026-11-13T19:45:00.000Z",
    "message_id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
    "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
    "chat_id": null,
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "removed_at": "2026-11-13T19:46:00.000Z"
  }
}
```

## Chats

### `chat.created`

The Mac saw a chat BlueBot didn’t know: a new group, or a first text with somebody.

A group chat’s id is what `chat_id` takes on a send.

```json title="chat.created"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "chat.created",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "chat_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "chat",
    "name": "Oak St closing",
    "is_group": true,
    "participants": [
      "+15555550123",
      "+15555550187"
    ],
    "service": "imessage",
    "last_message_at": "2026-11-13T19:41:00.000Z",
    "created_at": "2026-11-13T19:41:00.000Z"
  }
}
```

## Contacts

### `contact.created`

A person was added: by a send to somebody new, a text from somebody new, an import, the API or the dashboard.

`created_via` says how.

```json title="contact.created"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "contact.created",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "contact",
    "handle": "+15555550123",
    "kind": "phone",
    "first_name": "Maria",
    "last_name": "Alvarez",
    "company": null,
    "tags": [
      "buyer"
    ],
    "custom_fields": {},
    "stopped": false,
    "stopped_at": null,
    "stop_source": null,
    "last_service": "imessage",
    "first_message_at": null,
    "last_message_at": null,
    "created_via": "api",
    "created_at": "2026-11-13T19:41:00.000Z"
  }
}
```

### `contact.stopped`

A person went on your stop list. Their queued texts were canceled.

`stop_source: "reply"` means they texted a stop word such as STOP. BlueBot won’t text them again until they text START or you take them off the list.

```json title="contact.stopped"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "contact.stopped",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "contact",
    "handle": "+15555550123",
    "kind": "phone",
    "first_name": "Maria",
    "last_name": "Alvarez",
    "company": null,
    "tags": [
      "buyer"
    ],
    "custom_fields": {},
    "stopped": true,
    "stopped_at": "2026-11-13T19:50:00.000Z",
    "stop_source": "reply",
    "last_service": "imessage",
    "first_message_at": "2026-11-13T19:41:00.000Z",
    "last_message_at": "2026-11-13T19:41:00.000Z",
    "created_via": "api",
    "created_at": "2026-11-13T19:41:00.000Z"
  }
}
```

### `contact.unstopped`

A person came off your stop list: they texted START, or you took them off.

Texts to them go again from now on. Texts canceled while they were stopped stay canceled.

```json title="contact.unstopped"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "contact.unstopped",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "contact",
    "handle": "+15555550123",
    "kind": "phone",
    "first_name": "Maria",
    "last_name": "Alvarez",
    "company": null,
    "tags": [
      "buyer"
    ],
    "custom_fields": {},
    "stopped": false,
    "stopped_at": null,
    "stop_source": null,
    "last_service": "imessage",
    "first_message_at": "2026-11-13T19:41:00.000Z",
    "last_message_at": "2026-11-13T19:41:00.000Z",
    "created_via": "api",
    "created_at": "2026-11-13T19:41:00.000Z"
  }
}
```

## Automations

### `sequence.completed`

A person reached the end of a sequence, or it stopped early because they replied or stopped.

`state` and `stop_reason` say which.

```json title="sequence.completed"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "sequence.completed",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "enr_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "enrollment",
    "sequence_id": "seq_1Hq2LxW9cVbN3mKpRtYs0a",
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "handle": "+15555550123",
    "contact": {
      "id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
      "object": "contact",
      "handle": "+15555550123",
      "kind": "phone",
      "first_name": "Maria",
      "last_name": "Alvarez",
      "company": null,
      "tags": [
        "buyer"
      ],
      "custom_fields": {},
      "stopped": false,
      "stopped_at": null,
      "stop_source": null,
      "last_service": "imessage",
      "first_message_at": "2026-11-13T19:41:00.000Z",
      "last_message_at": "2026-11-13T19:41:00.000Z",
      "created_via": "api",
      "created_at": "2026-11-13T19:41:00.000Z"
    },
    "state": "stopped",
    "stop_reason": "replied",
    "completed_at": "2026-11-13T19:41:00.000Z"
  }
}
```

### `broadcast.completed`

Every text in a broadcast has settled: sent, failed, skipped, expired or canceled.

`counts` has the totals.

```json title="broadcast.completed"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "broadcast.completed",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "bc_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "broadcast",
    "name": "Open house Saturday",
    "state": "completed",
    "recipient_count": 42,
    "counts": {
      "sent": 12,
      "delivered": 27,
      "failed": 1,
      "skipped": 2
    },
    "completed_at": "2026-11-13T19:41:00.000Z"
  }
}
```

### `automation.matched`

An incoming text matched a keyword rule that has the “Send a webhook event” action.

Fires once per incoming text, for the first rule that matched. `message` is the text that matched. A stop word never matches a rule, so it never fires for one.

```json title="automation.matched"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "automation.matched",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "object": "automation_match",
    "rule_id": "kw_1Hq2LxW9cVbN3mKpRtYs0a",
    "rule_name": "Pricing",
    "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
    "actions": [
      "reply",
      "tag",
      "webhook"
    ],
    "message": {
      "id": "msg_2Bf8QzD4kJmA6nXwEuTr1c",
      "object": "message",
      "direction": "in",
      "state": "received",
      "to": "+15555550100",
      "from": "+15555550123",
      "chat_id": null,
      "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
      "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
      "text": "PRICING",
      "text_deleted": false,
      "files": [],
      "service_requested": "auto",
      "service": "imessage",
      "sms_retry": false,
      "source": "mac",
      "source_ref": null,
      "metadata": {},
      "hold": null,
      "send_at": null,
      "expires_at": null,
      "failure": null,
      "cancel_reason": null,
      "resolution": null,
      "resolved_at": null,
      "resent_message_id": null,
      "reply_to_id": null,
      "reactions": [],
      "edited_at": null,
      "unsent_at": null,
      "created_at": "2026-11-13T19:44:12.000Z",
      "queued_at": null,
      "sending_at": null,
      "sent_at": null,
      "delivered_at": null,
      "read_at": null,
      "failed_at": null,
      "unconfirmed_at": null,
      "expired_at": null,
      "canceled_at": null,
      "received_at": "2026-11-13T19:44:10.000Z"
    },
    "matched_at": "2026-11-13T19:41:00.000Z"
  }
}
```

## Your Mac

### `device.online`

The Mac checked in after being offline. Held texts start going.

Also fires the first time a newly connected Mac checks in.

```json title="device.online"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "device.online",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "dev_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "device",
    "status": "online",
    "name": "Dana’s MacBook Air",
    "number": "+15555550100",
    "app_version": "1.0.0",
    "macos_version": "15.1",
    "last_seen_at": "2026-11-13T19:41:00.000Z",
    "last_sync_at": "2026-11-13T19:41:00.000Z",
    "offline_since": null,
    "paired_at": "2026-11-01T15:00:00.000Z",
    "checks": {
      "messages": {
        "state": "ok",
        "detail": null
      },
      "full_disk_access": {
        "state": "ok",
        "detail": null
      },
      "forwarding": {
        "state": "ok",
        "detail": null
      },
      "awake": {
        "state": "ok",
        "detail": null
      },
      "filevault": {
        "state": "ok",
        "detail": null
      }
    },
    "checks_ok": true,
    "transcription": false,
    "held_count": 0,
    "queued_count": 0,
    "unconfirmed_count": 0,
    "paused_until": null,
    "today": {
      "sent": 14,
      "new_people": 3,
      "new_people_limit": 50,
      "daily_limit": 1000,
      "resets_at": "2026-11-14T05:00:00.000Z"
    },
    "failed_checks": []
  }
}
```

### `device.offline`

The Mac hasn’t checked in for 2 minutes. Texts wait with `hold.reason: "device_offline"`.

The owner is emailed once if it stays away 10 minutes.

```json title="device.offline"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "device.offline",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "dev_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "device",
    "status": "offline",
    "name": "Dana’s MacBook Air",
    "number": "+15555550100",
    "app_version": "1.0.0",
    "macos_version": "15.1",
    "last_seen_at": "2026-11-13T19:41:00.000Z",
    "last_sync_at": "2026-11-13T19:41:00.000Z",
    "offline_since": "2026-11-13T19:39:00.000Z",
    "paired_at": "2026-11-01T15:00:00.000Z",
    "checks": {
      "messages": {
        "state": "ok",
        "detail": null
      },
      "full_disk_access": {
        "state": "ok",
        "detail": null
      },
      "forwarding": {
        "state": "ok",
        "detail": null
      },
      "awake": {
        "state": "ok",
        "detail": null
      },
      "filevault": {
        "state": "ok",
        "detail": null
      }
    },
    "checks_ok": true,
    "transcription": false,
    "held_count": 2,
    "queued_count": 2,
    "unconfirmed_count": 0,
    "paused_until": null,
    "today": {
      "sent": 14,
      "new_people": 3,
      "new_people_limit": 50,
      "daily_limit": 1000,
      "resets_at": "2026-11-14T05:00:00.000Z"
    },
    "failed_checks": []
  }
}
```

### `device.check_failed`

One of the Mac’s setup checks turned `needs_you`, such as Messages signing out or Full Disk Access being turned off.

`failed_checks` names them and each check’s `detail` says what the Mac saw.

```json title="device.check_failed"
{
  "id": "evt_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "event",
  "type": "device.check_failed",
  "api_version": "2026-11-13",
  "timestamp": "2026-11-13T19:41:00.000Z",
  "data": {
    "id": "dev_1Hq2LxW9cVbN3mKpRtYs0a",
    "object": "device",
    "status": "online",
    "name": "Dana’s MacBook Air",
    "number": "+15555550100",
    "app_version": "1.0.0",
    "macos_version": "15.1",
    "last_seen_at": "2026-11-13T19:41:00.000Z",
    "last_sync_at": "2026-11-13T19:41:00.000Z",
    "offline_since": null,
    "paired_at": "2026-11-01T15:00:00.000Z",
    "checks": {
      "messages": {
        "state": "needs_you",
        "detail": "Messages isn’t signed in to iMessage."
      },
      "full_disk_access": {
        "state": "ok",
        "detail": null
      },
      "forwarding": {
        "state": "ok",
        "detail": null
      },
      "awake": {
        "state": "ok",
        "detail": null
      },
      "filevault": {
        "state": "ok",
        "detail": null
      }
    },
    "checks_ok": false,
    "transcription": false,
    "held_count": 0,
    "queued_count": 0,
    "unconfirmed_count": 0,
    "paused_until": null,
    "today": {
      "sent": 14,
      "new_people": 3,
      "new_people_limit": 50,
      "daily_limit": 1000,
      "resets_at": "2026-11-14T05:00:00.000Z"
    },
    "failed_checks": [
      "messages"
    ]
  }
}
```
