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

# POST /v1/channels/whatsapp/connect (QR mode)

> Multi-device QR pairing of a WhatsApp channel, scannable from the terminal or your own browser.

The `qr` mode of `POST /v1/channels/whatsapp/connect` pairs a channel without Embedded Signup: the client receives a QR, scans it with WhatsApp mobile (Settings -> Linked Devices -> Link a device) and when pairing completes receives the `channel_id` of the newly created channel. No browser step, no official WABA, no Cloud API. The integration uses WhatsApp multi-device under the hood.

This is what `keebai whatsapp connect --qr` does in the CLI.

## General flow

<Steps>
  <Step title="Your backend starts the session">
    `POST /v1/channels/whatsapp/connect` with `mode: "qr"`. Returns `session_id` + `stream_url` (relative SSE path).
  </Step>

  <Step title="Subscribe to the SSE stream">
    `GET {stream_url}` with `Accept: text/event-stream` and `Authorization: Bearer <PAT>`. You'll receive real-time events: `qr`, `connecting`, `connected`, `failed`, `disconnected`.
  </Step>

  <Step title="Show the QR to the user">
    Each `qr` event carries a string payload. Render it as a QR (in the terminal with `qrcode-terminal`, on web with `qrcode`, etc.). If the code expires before it's scanned, a new `qr` arrives automatically.
  </Step>

  <Step title="Receive the created channel">
    Once scanned successfully, the stream closes with a `connected` event whose `data` includes `channel_id`, `phone_number` and `push_name`.
  </Step>
</Steps>

## Start a QR session

`POST /v1/channels/whatsapp/connect`

**Required scope:** `channels:connect`

### Request

```bash theme={null}
curl -X POST https://api.keebai.com/v1/channels/whatsapp/connect \
  -H "Authorization: Bearer kbai_pk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "qr",
    "name": "WhatsApp Soporte"
  }'
```

| Field  | Type   | Required | Description                             |
| ------ | ------ | -------- | --------------------------------------- |
| `mode` | `"qr"` | yes      | Selects the QR flow.                    |
| `name` | string | no       | Channel label. Default `"WhatsApp QR"`. |

`pipeline_id` and `coexistence_enabled` don't apply to QR mode. If you send them, the API responds `400 QR_MODE_CANNOT_USE_COEXISTENCE_OR_PIPELINE`.

### Response 201

```json theme={null}
{
  "session_id": "cs_01HXYZ...",
  "provider": "whatsapp_qr",
  "mode": "qr",
  "stream_url": "/v1/channels/whatsapp/connect/cs_01HXYZ.../qr-stream",
  "expires_in": 300,
  "poll_interval": 5
}
```

| Field        | Type   | Description                                                                                                                 |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `session_id` | string | QR session id. Use it in the `stream_url` path.                                                                             |
| `stream_url` | string | Relative path of the SSE stream (prepend the API host).                                                                     |
| `expires_in` | number | Seconds until pairing expires if scanning isn't completed (5 minutes). The stream emits fresh QRs while the window is open. |

## SSE event stream

`GET /v1/channels/whatsapp/connect/:session_id/qr-stream`

**Required scope:** `channels:connect`

**Headers:** `Accept: text/event-stream`, `Authorization: Bearer kbai_pk_xxx`.

### Events

| Event          | When                                                                        | `data`                                                                      |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `qr`           | A QR code is ready to scan. Re-emitted when the code expires and refreshes. | string (QR payload) or `{ "data": "<qr-string>", "expires_at": "<iso>" }`   |
| `connecting`   | Mobile scanned the QR; WhatsApp is establishing the session.                | empty object or state                                                       |
| `connected`    | Pairing complete, channel created.                                          | `{ "channel_id": "ch_...", "phone_number": "+56...", "push_name": "Juan" }` |
| `failed`       | Pairing error (timeout, invalid code, linked-device limit, etc.).           | `{ "reason": "<description>" }`                                             |
| `disconnected` | The device was unlinked during pairing.                                     | `{ "reason": "<description>" }`                                             |

When `connected`, `failed` or `disconnected` arrives, the stream closes. The client should close the connection too.

### curl example

```bash theme={null}
curl -N \
  -H "Authorization: Bearer kbai_pk_xxx" \
  -H "Accept: text/event-stream" \
  https://api.keebai.com/v1/channels/whatsapp/connect/cs_01HXYZ.../qr-stream
```

```text theme={null}
event: qr
data: 2@AbcDe…/long-string

event: qr
data: 2@FghIj…/refresh

event: connecting
data: {}

event: connected
data: {"channel_id":"ch_01HXYZ...","phone_number":"+56987654321","push_name":"Juan"}
```

### Rendering the QR

In a terminal with `qrcode-terminal` (Node):

```ts theme={null}
import qrcode from "qrcode-terminal";
qrcode.generate(payload, { small: true });
```

On the web, with [`qrcode`](https://www.npmjs.com/package/qrcode):

```ts theme={null}
import QRCode from "qrcode";
QRCode.toCanvas(canvasEl, payload);
```

## CLI equivalent

```bash theme={null}
keebai whatsapp connect --qr [--name "WhatsApp Soporte"]
```

The CLI creates the session, opens the stream, draws the QR in the terminal, refreshes it automatically, and on scan prints `Channel ch_xxx connected (phone +56…, as Juan)`.

## Troubleshooting

<CardGroup cols={2}>
  <Card title="Expired QR">
    Pairing emits fresh QRs automatically every \~20 seconds. Don't restart the session: just wait for the next `qr` event.
  </Card>

  <Card title="4 linked-device limit">
    WhatsApp allows up to 4 devices per account. If the session ends with `failed` mentioning `device limit`, free a slot on the mobile (Settings -> Linked Devices) and call `POST /v1/channels/whatsapp/connect` again.
  </Card>

  <Card title="Expired session (5 min)">
    If nobody scanned, the stream closes and the `session_id` stops working. Request a new session.
  </Card>

  <Card title="Don't use Cloud API against this channel">
    QR channels don't support templates or bulk sends (Meta doesn't expose Cloud API on multi-device). A `template` message on `channel: "whatsapp_qr"` is rejected with `422 MESSAGE_TYPE_NOT_SUPPORTED_FOR_CHANNEL`, and `POST /v1/messages/bulk` returns `400 QR_NOT_SUPPORTED_FOR_TEMPLATE_OR_BULK`. Text and media work as usual.
  </Card>
</CardGroup>

## Sending messages

Once connected, QR channels show up in `GET /v1/whatsapp/numbers` with `connection_type: "qr"` and are used with the same endpoint as Cloud API ones:

```bash theme={null}
curl -X POST https://api.keebai.com/v1/messages \
  -H "Authorization: Bearer kbai_pk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp_qr",
    "channel_id": "665f1a2b3c4d5e6f70819234",
    "to": "+5491155555555",
    "message": { "type": "text", "body": "Hello from the QR channel" }
  }'
```
