> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blujay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Typing and read receipts

> Show a typing indicator, mark chats as read, and display your contacts' typing indicators from webhooks.

Typing indicators and read receipts make an automated conversation feel attended. You start and stop your typing indicator with `POST` and `DELETE /v1/chats/{chat_id}/typing`, mark a chat as read with `POST /v1/chats/{chat_id}/read`, and hear when your contacts are typing through the `chat.typing_indicator.started` and `chat.typing_indicator.stopped` webhooks.

## Show a typing indicator

Start the indicator before you send a reply, and stop it if you decide not to send one.

```bash curl theme={null}
curl -X POST https://api.blujay.io/v1/chats/chat_abc123/typing \
  -H "Authorization: Bearer blu_your_api_key"
```

```bash curl theme={null}
curl -X DELETE https://api.blujay.io/v1/chats/chat_abc123/typing \
  -H "Authorization: Bearer blu_your_api_key"
```

Both return `204 No Content` with an `X-Trace-ID` header. Typing is best effort: a `204` means Blujay accepted the request, not that the other person saw a typing bubble. Blujay doesn't promise how long the bubble stays up. Start it right before you send, and call `DELETE` if you decide not to send. What happens depends on the chat:

| Chat | Result |
| - | - |
| Direct chat confirmed as iMessage | The indicator shows on the other person's device. |
| Established iMessage group chat | The indicator shows to the group, from the chat's Blujay phone number. |
| SMS or RCS chat, a direct chat whose service isn't confirmed yet, or any other group | Accepted with `204`. Nothing is shown. |

Check the chat's `supports_typing_indicators` field from `GET /v1/chats/{chat_id}` to know in advance whether an indicator can appear. You can call the typing endpoints either way.

| Error | Status | Cause |
| - | - | - |
| [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled) | 400 | Typing indicators are switched off for your workspace. They're on by default and there's no setting in the API; contact [support@blujay.io](mailto:support@blujay.io) to switch them back on. |
| [`UNSUPPORTED_CHAT_OPERATION`](/errors/unsupported-chat-operation) | 400 | The chat isn't active. See [Glossary](/glossary.md). |
| [`PHONE_NUMBER_UNAVAILABLE`](/errors/phone-number-unavailable) | 503 | Your Blujay phone number couldn't be reached. Retry, or skip the indicator. |

## Mark a chat as read

`POST /v1/chats/{chat_id}/read` marks every incoming message in the chat as read in Blujay. It works on the whole chat; there is no per-message read, and the call takes no body.

```bash curl theme={null}
curl -X POST https://api.blujay.io/v1/chats/chat_abc123/read \
  -H "Authorization: Bearer blu_your_api_key"
```

It returns `204 No Content`. In direct iMessage and RCS chats, Blujay also sends a read receipt, so the other person can see that you read their messages. SMS chats and group chats are only marked read in Blujay; nobody is notified. Mark-read ignores `Idempotency-Key`; calling it twice is harmless.

A `503` [`PHONE_NUMBER_UNAVAILABLE`](/errors/phone-number-unavailable) means the read receipt couldn't be sent. The chat is still marked read in Blujay, so you can retry or move on.

## Receive typing indicators

Subscribe to `chat.typing_indicator.started` and `chat.typing_indicator.stopped` to hear when a contact is typing to one of your numbers in a direct iMessage chat. SMS and RCS chats never produce typing events, and your own typing calls never produce them.

```json chat.typing_indicator.started theme={null}
{
  "api_version": "v1",
  "webhook_version": "2026-10-03",
  "event_type": "chat.typing_indicator.started",
  "event_id": "evt_abc123",
  "created_at": "2026-10-14T17:40:00.100Z",
  "trace_id": "trc_2b4d6f8a0c1e",
  "workspace_id": "workspace_abc123",
  "subscription_id": "whsub_abc123",
  "data": {
    "chat_id": "chat_abc123",
    "phone_number_id": "phone_abc123",
    "occurred_at": "2026-10-14T17:40:00.000Z",
    "expires_at": "2026-10-14T17:41:00.000Z"
  }
}
```

`occurred_at` is when Blujay observed the typing, not the exact keystroke time. `expires_at` is always 60 seconds after `occurred_at`.

### Group typing snapshots

Subscriptions on webhook version `2026-10-03` also receive typing for iMessage group chats, but only on Dedicated numbers (`access_type: "dedicated"` in [`GET /v1/phone-numbers`](/phone-numbers)). Earlier versions never receive group typing, and a Free Plan number never does. A group event uses the same two event types and adds `participants`: everyone typing in that group right now.

```json chat.typing_indicator.started (group) theme={null}
{
  "api_version": "v1",
  "webhook_version": "2026-10-03",
  "event_type": "chat.typing_indicator.started",
  "event_id": "evt_def456",
  "created_at": "2026-10-14T17:45:00.100Z",
  "trace_id": "trc_6f8a0c2e4b1d",
  "workspace_id": "workspace_abc123",
  "subscription_id": "whsub_abc123",
  "data": {
    "chat_id": "chat_grp789",
    "phone_number_id": "phone_abc123",
    "occurred_at": "2026-10-14T17:45:00.000Z",
    "expires_at": "2026-10-14T17:46:00.000Z",
    "participants": [
      { "type": "phone", "address": "+15555550123", "contact_id": "contact_abc123" },
      { "type": "phone", "address": "+15555550125" }
    ]
  }
}
```

* `started` always lists at least one participant. `stopped` has `"participants": []`.
* There are no per-person events. When one person stops while another keeps typing, you get another `started` with the shorter list. Replace the chat's whole list of typers with each newer snapshot.
* Each participant has `type` (`phone` or `email`) and `address`, plus `contact_id` when they match a contact in your workspace. Your own Blujay numbers never appear.

### Display typing correctly

Typing events can be repeated, delayed or out of order, and a `stopped` event isn't guaranteed. Apply these rules for each `chat_id` and `phone_number_id` pair:

### 1. Deduplicate

Ignore any `event_id` you have already processed.

### 2. Drop stale events

Ignore an event whose `expires_at` has passed, or whose `occurred_at` is older than the last event you applied.

### 3. Break ties toward stop

If a `started` and a `stopped` have the same `occurred_at`, apply the `stopped`.

### 4. Expire on your own

Clear the indicator at `expires_at` even if no `stopped` arrives.

A refreshed indicator arrives as another `started` event with a later `occurred_at` and `expires_at`.

> [!WARNING]
>
> **Common mistakes**
>
> * A `204` from `POST /typing` doesn't mean a bubble appeared. Check `supports_typing_indicators` on the chat if it matters.
> * `POST /read` marks the whole chat, not one message.
> * A `stopped` event isn't guaranteed. Clear the indicator at `expires_at` on your own.
> * Group typing events need webhook version `2026-10-03` and a Dedicated number. On older versions or a Free Plan number, you'll never see them.

## Limitations

* Typing indicators you send appear only in direct iMessage chats and established iMessage groups. Everything else is accepted with `204` and shows nothing.
* No duration is promised for a typing bubble, and Blujay doesn't confirm that it was shown.
* Typing can be switched off for a workspace (`400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled)); only support can switch it back on.
* Read receipts go out for direct iMessage and RCS chats only. SMS and group chats are marked read in Blujay without notifying anyone.
* Mark-read covers the whole chat. There is no per-message read receipt.
* Incoming typing events come from direct iMessage chats, and from iMessage groups only on Dedicated numbers with webhook version `2026-10-03`. SMS and RCS chats never produce them.
* Your own typing calls never produce typing events.

## Next steps

> **[Webhooks overview](/webhooks/overview.md)**
>
> Create a subscription for typing and message events.

> **[Webhook versions](/webhooks/versions.md)**
>
> Move to `2026-10-03` to receive group typing snapshots.

> **[Group chats](/messaging/group-chats.md)**
>
> Learn what makes a group an established iMessage group.

> **[Send messages](/messaging/send-messages.md)**
>
> Send the reply after your typing indicator.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.