Channels
A channel is a number you can send from, and the provider behind it is the only thing that differs between them. Choosing one is the first real decision in an integration, so this page states plainly what each provider does, what it costs you in risk, and what is not built yet.
Three providers
The send API is identical across all of them. What differs is how you get the number, what the number can do, and what happens to it over time.
| Provider | Availability | Description |
|---|---|---|
| sandbox | Available | A shared test number the platform owns. Instant, free, and restricted to recipients who have verified they own their number. |
| qr | Available | The customer's own WhatsApp account, linked by scanning a QR code. Immediate, and unofficial — see the policy note below. |
| meta | Not yet available | The official WhatsApp Business Platform. No adapter is deployed, so creating one is refused with a validation error naming the providers this environment does support. |
Endpoints
Deleting a channel archives it rather than destroying it: the messages sent through it stay readable, and the operation is repeatable.
curl https://whats.azzamkh.sa/api/v1/channels \
-H "Authorization: Bearer $WA_API_KEY"
# 200 OK
# {
# "data": [{
# "id": "ch_...",
# "name": "Sandbox",
# "provider": "sandbox",
# "status": "connected",
# "phone_number": "+999...",
# "is_default": true,
# "capabilities": {
# "send_text": true,
# "send_media": false,
# "send_local_template": true,
# "send_meta_template": false,
# "campaigns": false,
# "max_throughput_mps": 2
# },
# "capabilities_refreshed_at": "2026-08-13T10:00:00.000Z"
# }],
# "page": { "next_cursor": null, "has_more": false }
# }Channel status
A send is only accepted from a connected channel; every other status is refused with CHANNEL_NOT_CONNECTED.
| Status | Description |
|---|---|
| pending | Created, and not yet connected to anything. |
| connecting | The provider is being contacted. |
| awaiting_scan | A QR code is waiting to be scanned. |
| connected | Ready. The only status a send is accepted from. |
| degraded | Connected but unhealthy — the provider is unreachable or failing. Sends may still be accepted and may still fail. |
| disconnected | No longer linked. A QR channel reaches this after a logout. |
| suspended | Stopped by the platform. |
| archived | Retired by the workspace. Its history stays readable. |
Capabilities
Each channel publishes what its provider can actually do. Read it rather than assuming — send_media is false everywhere today, and a capability a channel does not have is a 409, not a silent no-op.
| Capability | sandbox | qr |
|---|---|---|
| send_text | true | true |
| send_media | false | false |
| send_local_template | true | true |
| send_meta_template | false | false |
| campaigns | false | false |
| official_meta | true | false |
| business_initiated_templates | false | false |
| message_status | true | true |
| read_receipts | true | true |
| coexistence | false | false |
Throughput is published with them: 2 messages per second on a sandbox channel and 0.5 on a QR one.
A snapshot, not a live query
Capabilities come from a stored snapshot. Until one has been taken, the capabilities endpoint answers 409 with details.reason = "capabilities_not_refreshed" rather than guessing.
Sandbox
The sandbox is a pool of platform-owned test numbers, shared between workspaces. Allocating one creates a channel and a session; the session is what carries the verification and the expiry.
The rules that will surprise you
- Every sandbox operation requires a
test-mode API key. A live key is a 403. - You can only message a recipient who has proven ownership by sending the verification code from their own handset. Any other recipient is refused.
- The allocation expires after
24hours, after which sends are refused and you allocate again. - The pool is finite. When every healthy number is at capacity the allocation is a 503, not a queue.
- You cannot pick a number. Allocation picks one for you, and never one already serving the same recipient for someone else.
The quickstart walks the whole flow end to end.
QR — the customer's own number
This is not the official WhatsApp path
QR linking drives WhatsApp's own multi-device protocol as an unofficial client. WhatsApp's policies restrict unauthorised clients, bulk messaging and automated messaging, and an account connected this way can be restricted or banned. No technical limit can make it safe — the platform will never describe QR as an official Meta integration, and will never claim an account cannot be banned.
The safety limit below is a platform policy chosen to be conservative. It is not a WhatsApp guarantee, and staying under it guarantees nothing.
A QR channel is created through the normal endpoint, then paired by scanning a code with the phone that owns the account.
curl -X POST https://whats.azzamkh.sa/api/v1/channels \
-H "Authorization: Bearer <access token>" \
-H "X-Workspace-Id: ws_..." \
-H "Content-Type: application/json" \
-d '{ "name": "Support line", "provider": "qr" }'
# 201 Created { "id": "ch_...", "provider": "qr", "status": "pending", ... }
# Pairing itself is a browser flow: POST .../qr/start, then read the codes
# from the SSE stream. Those four routes do NOT accept an API key.The pairing flow
Pairing is a browser flow, and its four routes accept a user access token only — an API key is refused. The API records the intent; a worker holds the socket and produces the codes, which arrive over the event stream rather than in a poll response.
The limits on a QR channel
| Limit | Value | Description |
|---|---|---|
| Safety limit | 200 / 24h | Outbound messages per rolling window per channel. Exceeding it is a 429 with QR_SAFETY_LIMIT_REACHED. |
| Same-recipient rate | 6 / min | Messages per minute to any one recipient. |
| Throughput | 0.5 msg/s | The rate the dispatcher will hand messages to the provider. |
| Code validity | 60s | How long one displayed QR code is valid before it is replaced. |
Meta Cloud API
Not yet available
No Meta adapter is deployed. Creating a channel with that provider is refused with a 422 whose details.available lists the providers this environment does support. There is no onboarding endpoint and no template submission endpoint.
The API was designed so that this is a channel swap and not a rewrite: the send request, the message shape, the webhook events and the error codes are all provider-independent. When Meta arrives, an integration written against the sandbox or against QR changes the channel it names and nothing else.