> 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) today, so everything on this page needs a line with WhatsApp connected.

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.

## List your groups

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

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

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

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

**`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 line can't answer in time, its groups come 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 another line's groups. Retry if you need the name.

## Send into a group

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

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

```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.

Group messages are WhatsApp messages, so everything on that channel applies: [text only on the way out](/documentation/capabilities/whatsapp), and one send at a time per line.

## Read a group's messages

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

```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.

**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:

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

```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, both mean **every** recipient. A message is `delivered` once every recipient's device has it, and `read` once the read count reaches the recipient count — never "somebody read it".

**Finding out about a *new* group is a poll.** 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.

**iMessage groups aren't supported yet.** Every group is a WhatsApp group today, and a `groupId` on any other channel is rejected with `400`. The group ID itself is channel-neutral, so iMessage groups will slot into this same shape when they land — it's on the roadmap.

**Typing indicators inside a group aren't supported yet** either. Also on the roadmap.

## 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.