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

# Karibu WhatsApp API

> WhatsApp API overview - manage conversation threads and send text/template/media/interactive messages. Workspace-scoped, envelope responses.

Read and manage WhatsApp conversation threads, send messages, and look up senders and templates. Base path `https://karibu.briq.tz/v1/whatsapp/*`. Every endpoint is **workspace-scoped** (resolved from your `X-API-Key`).

<Note>
  **Conversations** (`/conversations/*`) read and manage threads. **Messages** (`/messages/*`) send and track. **Senders** (`/senders/*`) and **Templates** (`/templates/*`) are read-only lookups. Sends are async: **202** with `{ message_id, status: "pending" }`, then poll or webhook.
</Note>

<Note>
  Conversations and Messages use the envelope below. Senders and Templates are older reads that return the data shape **directly** (no envelope) and a plain `404 { "detail": "..." }`.
</Note>

Try any endpoint live from its page under **Karibu WhatsApp** in the API reference — add your `X-API-Key` and send.

## Envelope

```json theme={null}
{ "success": true, "data": { }, "errors": null, "request_id": "uuid" }
```

On failure: `success: false`, `errors: [{ code, message, field }]`.

## Conversations

| Method   | Path                       | Does                                                             |
| -------- | -------------------------- | ---------------------------------------------------------------- |
| `GET`    | `/conversations`           | List/search threads (`status`, `sender`, `recipient`, `search`). |
| `GET`    | `/conversations/summary`   | Inbox counts (total/open/unread/unassigned).                     |
| `GET`    | `/conversations/{id}`      | One thread.                                                      |
| `POST`   | `/conversations/{id}/read` | Clear unread state.                                              |
| `DELETE` | `/conversations/{id}`      | Soft-delete (one-way).                                           |

To read a thread's messages, filter the messages collection: `GET /messages?conversation_id={id}`. To find a thread by customer number: `GET /conversations?recipient={phone}`.

## Messages

Each message type has its own send endpoint with a payload scoped to exactly its fields. Open any page and use **Try it** to send a live request.

**Send messages**

| Type             | Page                                                                      | Notes                                                  |
| ---------------- | ------------------------------------------------------------------------- | ------------------------------------------------------ |
| `text`           | [Send a text message](/Karibu-WhatsApp/messages/send-text)                | Plain text, ≤4096 chars. Needs an open window.         |
| `template`       | [Send a template message](/Karibu-WhatsApp/messages/send-template)        | Approved template; always allowed, reopens the window. |
| `media` IMAGE    | [Send an image](/Karibu-WhatsApp/messages/send-image)                     | JPEG/PNG, ≤5MB, optional caption.                      |
| `media` VIDEO    | [Send a video](/Karibu-WhatsApp/messages/send-video)                      | MP4/3GPP, ≤16MB, optional caption.                     |
| `media` DOCUMENT | [Send a document](/Karibu-WhatsApp/messages/send-document)                | PDF/Office/text, ≤100MB, caption + filename.           |
| `media` AUDIO    | [Send an audio message](/Karibu-WhatsApp/messages/send-audio)             | AAC/MP3/M4A/OGG-OPUS/AMR, ≤16MB, no caption.           |
| `interactive`    | [Send an interactive message](/Karibu-WhatsApp/messages/send-interactive) | Buttons or list. Needs an open window.                 |

**Track messages**

| Method | Path                  | Page                                                                                              |
| ------ | --------------------- | ------------------------------------------------------------------------------------------------- |
| `GET`  | `/messages`           | [List messages](/Karibu-WhatsApp/messages/list) — poll messages and inbound replies.              |
| `GET`  | `/messages/{id}`      | [Get message status](/Karibu-WhatsApp/messages/get-status) — status, derived error, paired reply. |
| `POST` | `/messages/{id}/read` | [Send read receipt](/Karibu-WhatsApp/messages/read-receipt) — blue ticks for an inbound message.  |

## Senders

| Method | Path                   | Does                                                                                                    |
| ------ | ---------------------- | ------------------------------------------------------------------------------------------------------- |
| `GET`  | `/senders`             | List sender numbers (`is_active`, `is_default`).                                                        |
| `GET`  | `/senders/{sender_id}` | One sender. Use its number, `phone_number_id`, or `id` as the `sender` value when sending or filtering. |

## Templates

| Method | Path                       | Does                                                                                                               |
| ------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `GET`  | `/templates`               | List templates, cursor-paginated (`status`, `category`, `language`, `name_or_content`, `cursor`, `limit`, `sort`). |
| `GET`  | `/templates/{template_id}` | One template. Send by `name`, not id; only `APPROVED` templates send.                                              |

## Rules

* **One send endpoint, one send shape.** `POST /messages`, discriminated by `type` (`text` / `template` / `media` / `interactive`). Provide exactly one target: `to` for first contact, or `conversation_id` to reply (recipient and sender then come from the thread). Sending both, or neither, is **422 `VALIDATION_ERROR`**.
* **Async sends.** A send returns **202** `{ message_id, status: "pending" }`. Track via `GET /messages/{id}` or a webhook.
* **Window.** Templates always go and reopen the 24h window; `text` / `media` / `interactive` need an open window or return **422 `WINDOW_CLOSED`**. First contact should be a template.
* **Friendly sender.** Filter conversations or pick a send sender with `sender`: its WhatsApp number, its `phone_number_id`, or its sender id (UUID). The UUID-only `sender_id` still works but is deprecated. Conversations and sends echo `sender` (the `phone_number_id`) back.
* **Internal ids everywhere.** Endpoints take your internal `message_id` / `conversation_id` (not provider `wamid`s). The read receipt resolves the provider id for you.
* **Phone format:** E.164 digits, optional leading `+` (e.g. `255712000000`). The send `to` and the `?recipient=` filter are validated/normalised the same way.
* **`window`** (`{open, expires_at}`) reflects the 24-hour service window.
* **Mark-read** on a conversation clears inbox unread state; `/messages/{id}/read` is the WhatsApp read receipt.
* **Delete** is soft and one-way; the thread leaves all lists.

## Errors

| Code                                                         | HTTP      | Meaning                                                                                                                      |
| ------------------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `WORKSPACE_REQUIRED`                                         | 403       | Key not linked to a workspace.                                                                                               |
| `VALIDATION_ERROR`                                           | 422       | Missing content for the `type`, or not exactly one of `to` / `conversation_id`.                                              |
| `CONVERSATION_NOT_FOUND`                                     | 404       | Not in your workspace (or deleted).                                                                                          |
| `MESSAGE_NOT_FOUND`                                          | 404       | Message not in your workspace.                                                                                               |
| `INVALID_MESSAGE`                                            | 422       | Read receipt target is outbound or has no provider id.                                                                       |
| `WINDOW_CLOSED`                                              | 422       | 24h window closed; send a template.                                                                                          |
| `TEMPLATE_NOT_APPROVED` / `TEMPLATE_NOT_FOUND_OR_UNAPPROVED` | 422 / 404 | Template not approved or unknown.                                                                                            |
| `TEMPLATE_PARAM_COUNT_MISMATCH`                              | 400       | `variables` count does not match the template.                                                                               |
| `NO_TEMPLATE_LINK`                                           | 404       | Sender does not own the named template.                                                                                      |
| `NO_SENDER` / `NO_ACTIVE_SENDER`                             | 400       | No usable sender resolved.                                                                                                   |
| `SENDER_NOT_FOUND` / `SENDER_INACTIVE`                       | 404 / 400 | Given `sender` is not in the workspace or not active.                                                                        |
| `SENDER_AMBIGUOUS`                                           | 409       | The `sender` number matches more than one sender. Pass the `phone_number_id` or sender id instead.                           |
| `RECIPIENT_UNDELIVERABLE` / `PHONE_NOT_REGISTERED`           | 422       | Recipient unreachable or not on WhatsApp.                                                                                    |
| `PROVIDER_UNAVAILABLE`                                       | 422       | Upstream provider could not process the send. No message was sent; retry shortly. `data.message_id` is present and pollable. |
| `WHATSAPP_REAUTH_REQUIRED`                                   | 422       | WhatsApp account credentials expired; reconnect the account, then retry.                                                     |
| `SEND_FAILED`                                                | 422       | The send could not be processed. Retry shortly.                                                                              |

This API never returns a 5xx. Every failure mode is a stable `4xx` code above, so your integration can branch on `errors[0].code` without special-casing server faults. Transient provider problems surface as `422 PROVIDER_UNAVAILABLE` (retry), and when a record was created the response still carries a pollable `data.message_id`.

Walkthroughs with examples: [conversations guide](/guides/whatsapp-conversations) · [messages guide](/guides/whatsapp-messages) · [senders and templates guide](/guides/whatsapp-senders-templates).

## Webhook events

Sends are async, so delivery status arrives by webhook (poll `GET /messages/{id}` as the pull alternative). Register a `whatsapp` webhook for your developer app (see [Karibu Webhooks](/Karibu-Webhooks/index)); Briq POSTs each event to it.

| Event                | When                                              |
| -------------------- | ------------------------------------------------- |
| `whatsapp.sent`      | Accepted by the provider.                         |
| `whatsapp.delivered` | Delivered to the recipient device.                |
| `whatsapp.read`      | Read by the recipient.                            |
| `whatsapp.failed`    | Send failed (adds `error_code`, `error_message`). |
| `whatsapp.received`  | Inbound message from a customer.                  |

Every callback is an HTTP POST with this envelope:

```json theme={null}
{
  "event": "whatsapp.delivered",
  "event_id": "evt_...",
  "app_id": "your-app-id",
  "service_type": "whatsapp",
  "timestamp": "2026-06-24T09:12:00Z",
  "data": {
    "message_id": "uuid",
    "conversation_id": "uuid",
    "provider_message_id": "wamid....",
    "recipient": "255712345678"
  }
}
```

* `data.message_id` matches the `message_id` from your send response and `GET /messages/{id}` — use it to correlate.
* `whatsapp.failed` adds `data.error_code` and `data.error_message`.
* `whatsapp.received` carries `from`, `message_type`, `content`, and `profile_name` instead of `recipient`.
* Status events for messages you sent via the API are routed to your webhook automatically (correlated by your API key); inbound events are routed by workspace.

<Note>
  Events are **at-least-once** — the same `event_id` may arrive more than once. Deduplicate on `event_id` (or `event` + `data.message_id`).
</Note>

## Verifying the signature

When your webhook has a signing secret, each request carries `X-Briq-Signature` plus `X-Briq-Service-Type` and `X-Briq-App-ID`. The signature is the HMAC-SHA256 hex digest of the JSON body **re-serialized with sorted keys**, keyed by your secret:

```python theme={null}
import hashlib, hmac, json

def verify(raw_body: bytes, signature: str, secret: str) -> bool:
    payload = json.loads(raw_body)
    expected = hmac.new(
        secret.encode(),
        json.dumps(payload, sort_keys=True).encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
```

<Warning>
  Sign over `json.dumps(payload, sort_keys=True)`, not the raw bytes — key order is normalized before signing. Reveal or rotate the secret from [Karibu Webhooks](/Karibu-Webhooks/index).
</Warning>
