> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getdial.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getdial.ai/_mcp/server.

# Group conversations

> Send to and read from group conversations, which Dial addresses by group ID rather than by phone number.

A group is a conversation that **isn't a phone number**. Dial gives each one an ID of its own, and you address it the way you'd otherwise address a recipient — with `groupId` instead of `to`.

Groups exist on [WhatsApp lines](/documentation/capabilities/whatsapp) and on iMessage numbers. The group ID is channel-neutral — the same field, the same endpoints, whichever channel the conversation is on.

> **Note**
>
> Your line has to already be **in** the group. Dial can send into a group someone added your number to; it can't create one, join one, or accept an invite link on your behalf.

### What each channel can do in a group

|                                                     | WhatsApp                         | iMessage |
| --------------------------------------------------- | -------------------------------- | -------- |
| Send text                                           | Yes                              | Yes      |
| Send media                                          | Yes — one attachment per message | Yes      |
| Typing indicator                                    | Yes                              | Yes      |
| Reply to, or react to, a message **inside** a group | Yes                              | No       |

Everything else on this page works the same on both.

## List your groups

**`CLI`**

```bash title="CLI"
dial group list
```

**`cURL`**

```bash title="cURL"
curl https://api.getdial.ai/v1/groups \
  -H "Authorization: Bearer sk_live_..."
```

```json
{
  "groups": [
    { "id": "grp_123", "channel": "whatsapp", "name": "Planning bday party", "createdAt": "2026-09-01T10:22:04.000Z" },
    { "id": "grp_456", "channel": "imessage", "name": null, "createdAt": "2026-09-02T18:03:41.000Z" }
  ]
}
```

`channel` is the channel the group is on — `whatsapp` or `imessage`, the same value its messages carry — and it decides [what you can do there](#what-each-channel-can-do-in-a-group). A group never changes channel.

`createdAt` is when **Dial** first learned of the group — either your line being added to it, or the first message that named it.

> **Note**
>
> **`name` can be `null`, and that isn't an error.** Participants rename groups whenever they like, so Dial doesn't keep a copy of the subject — a stored name would be a cache with nothing to invalidate it. The name is read live from the line holding the conversation instead.
>
> When a lookup can't answer in time, that group comes back with `name: null` rather than failing the whole request. The IDs are Dial's own and always available, so one quiet line never hides a group or blanks another one's name. Retry if you need the name.

## Send into a group

Give `groupId` where you'd normally give `to`:

**`CLI`**

```bash title="CLI"
dial message --group grp_123 --body "On my way"
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/messages \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"groupId":"grp_123","body":"On my way"}'
```

**`to` and `groupId` are mutually exclusive.** Exactly one — sending both, or neither, is rejected with `400`.

**You don't need to say which number it goes out from.** A group already belongs to a line, so Dial uses that one and `fromNumber` / `fromNumberId` are optional here. You *may* pass one, and if it names a different line than the group's, the send is **refused** with `400` rather than quietly sent from somewhere else — a message that left from an unexpected number can't be taken back.

**A group send is an ordinary message on that group's channel**, so the channel's own rules apply. On a WhatsApp group that means [at most one attachment per message](/documentation/capabilities/whatsapp#what-whatsapp-does-differently), and one send at a time per line. On an iMessage group, `media` works exactly as it does in a one-to-one conversation.

**Naming a `channel` that the group isn't on is refused** with a `400` rather than sent on the other one — a message delivered to a rail you didn't ask for can't be taken back. You can leave `channel` out entirely; the group already knows which one it's on.

## Show a typing indicator in a group

Give `groupId` where you'd normally give `toNumber`. Every member of the group sees it.

**`CLI`**

```bash title="CLI"
dial typing start --group grp_123
dial typing stop --group grp_123
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/typing \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"groupId":"grp_123","value":true}'
```

The same rules as a one-to-one indicator apply: it's fire-and-forget and free, nothing is persisted and no event is emitted, and **sending a message into the group clears it natively** — so start it again after a send if you're still composing. Dial never clears it for you; send `value: false` when you stop composing without sending.

**`toNumber` and `groupId` are mutually exclusive here too**, and as with a send, `fromNumber` is optional because the group already names its line.

**Leave `channel` out.** A group is on exactly one channel, so the group already says which rail to show the indicator on; naming a different one is rejected with `400`.

## Read a group's messages

**`CLI`**

```bash title="CLI"
dial message list --group grp_123
```

**`cURL`**

```bash title="cURL"
curl "https://api.getdial.ai/v1/messages?groupId=grp_123" \
  -H "Authorization: Bearer sk_live_..."
```

`groupId` combines with the other filters, so `?groupId=grp_123&direction=inbound` is just the inbound half of that conversation.

> **Warning**
>
> **On a group message, `to` is `null`** — in both directions. The message is addressed to the group, and the group is not a phone number, so there's nothing to put there. `groupId` carries the destination instead.
>
> **Which of your numbers the conversation is on is `phoneNumberId`.** That field is set on every message Dial has ever recorded, group or not. If your integration reads `to` as "my number", that's the line to change — on a group message it's the *destination*, and the destination is the group.

```json
{
  "id": "msg_789",
  "phoneNumberId": "pn_123",
  "from": "+14155550123",
  "to": null,
  "groupId": "grp_123",
  "body": "I'll bring the cake",
  "direction": "inbound",
  "channel": "whatsapp"
}
```

On an inbound group message, `from` is **the participant who actually sent it** — not the group, and not your own line.

## React to group messages

Inbound group messages arrive as ordinary [`message.received`](/api-reference/events/message-received) events carrying `groupId`, so you can wait for one group's traffic specifically:

**`CLI`**

```bash title="CLI"
dial wait-for message.received --field groupId=grp_123 --timeout 60
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/events/wait \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"eventType":"message.received","filters":{"groupId":"grp_123"},"timeout":60}'
```

Delivery and read receipts work as they do elsewhere, with one difference worth knowing: in a group, `read` means **every** recipient. A message counts as read once every participant has read it — never "somebody read it".

> **Note**
>
> **On an iMessage group, a `read` may never arrive, and that isn't a failure.** Read receipts are something each recipient's device chooses to send — the setting is off by default — and a receipt that can't be attributed to a particular reader is discarded rather than counted as a guess. So the tally is never wrong, but it can stay incomplete indefinitely. Don't build a flow that waits for `read` in a group; treat it as confirmation when it comes and nothing when it doesn't.
>
> **Delivery in an iMessage group is inferred**, because the channel sends no delivery receipts of its own: the first read Dial sees proves the message reached at least one device, so `deliveryState` advances then. On a WhatsApp group it stays what it is elsewhere on that channel.

> **Note**
>
> **Finding out about a *new* group is a poll, on both channels.** There's no join event: when someone adds your line to a group, that shows up as a new entry from `GET /v1/groups` (and the group's first message arrives normally either way). List groups when you need to know what's there.

## Next

#### [WhatsApp lines](/documentation/capabilities/whatsapp)

Connect WhatsApp to a number.

#### [Stream account events](/documentation/platform/stream-account-events)

Keep a live event stream open.