# WhatsApp

Connect a WhatsApp Business number, send templates and replies, run broadcasts, flows, groups and calls, and know the 24-hour window and tier rules.

WhatsApp in Pozy runs on Meta's WhatsApp Business Platform. You connect a WhatsApp Business number to a profile, then message customers from the [Inbox](https://pozy.ai/docs/inbox) API, send [broadcasts](https://pozy.ai/docs/whatsapp/broadcasts), and use templates, flows, groups and calling. The platform id is `whatsapp`.

This page is the summary. The [WhatsApp section](https://pozy.ai/docs/whatsapp) has a page for each feature.

## What you can do

| Feature | Supported | Where |
| --- | --- | --- |
| Text, media, documents, audio and voice notes | Yes | [Sending messages](#sending-messages) |
| Templates (marketing, utility, authentication) | Yes | [Templates](https://pozy.ai/docs/whatsapp/templates) |
| Interactive messages (lists, buttons, CTA links, flows) | Yes | [Flows](https://pozy.ai/docs/whatsapp/flows) |
| Broadcasts, scheduled | Yes | [Broadcasts](https://pozy.ai/docs/whatsapp/broadcasts) |
| Group chats | Yes, on Cloud API only numbers | [Groups](https://pozy.ai/docs/whatsapp/groups) |
| Voice calling, inbound and outbound | Yes, on Cloud API only numbers | [Calling](https://pozy.ai/docs/whatsapp/calling) |
| AI business agent | Yes | [Business agent](https://pozy.ai/docs/whatsapp/business-agent) |
| Free test number | Yes | [Sandbox](https://pozy.ai/docs/whatsapp/sandbox) |
| Click-to-WhatsApp ads | Yes | [Meta Ads](https://pozy.ai/docs/ads/meta-ads) |
| Scheduling a single message | No | Schedule a broadcast instead |
| Post analytics, comments | No | Delivery and read status per message only |

## Two rules that shape every send

**The 24-hour window.** For 24 hours after a customer's last message you can send them anything. Outside that window, the first message must be an approved template. A free-form message outside the window fails with Meta's error 131047.

**The messaging tier.** A new number starts on Meta's lowest tier: 250 unique contacts in 24 hours for business-initiated conversations. Meta raises the tier as you build history and keep quality up. Quality and tier changes arrive as the `whatsapp.account.quality_updated` webhook.

## Connect

You need a WhatsApp Business Account inside a Meta Business account. A personal WhatsApp account cannot be connected. If the business has no WhatsApp Business Account yet, Meta creates one during signup. There are three ways in:

| Route | When to use it | How |
| --- | --- | --- |
| Meta's Embedded Signup | The business connects its own number in the browser | [Get OAuth connect URL](https://pozy.ai/docs/api/getConnectUrl) with platform `whatsapp` |
| System User token | You already manage the WhatsApp Business Account in Meta and want a headless connect | [Connect WhatsApp via credentials](https://pozy.ai/docs/api/connectWhatsAppCredentials) with `accessToken`, `wabaId` and `phoneNumberId` |
| A number bought through Pozy | You want a new number that can also carry calls and SMS | [WhatsApp phone numbers](https://pozy.ai/docs/whatsapp/phone-numbers) |

```bash cURL
curl "https://api.pozy.ai/v1/connect/whatsapp?profileId=66a1f0c2a4b9d3e8f1a2b3c4&redirect_url=https://app.example.com/connected&onboarding=api" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/connect/whatsapp?profileId=66a1f0c2a4b9d3e8f1a2b3c4&redirect_url=https:%2F%2Fapp.example.com%2Fconnected&onboarding=api", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// Send the user's browser to data.authUrl
```
```python Python
import os, requests

resp = requests.get(
    "https://api.pozy.ai/v1/connect/whatsapp",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    params={"profileId": "66a1f0c2a4b9d3e8f1a2b3c4", "redirect_url": "https://app.example.com/connected", "onboarding": "api"},
)
print(resp.json()["authUrl"])
```

The user lands back on `redirect_url` with the new `accountId`, which every WhatsApp call takes from then on.

### Cloud API only or coexistence

`onboarding` decides whether the number keeps working in the WhatsApp Business app on the phone:

| `onboarding` | What happens | Trade-off |
| --- | --- | --- |
| `business_app` (the default when you leave it out) | Coexistence. The number stays in the WhatsApp Business app and also works on the API. The user scans a QR code Meta shows during signup | Throughput fixed at 20 messages per second. No group chats, no calling. Meta has no API to leave coexistence later |
| `api` | Cloud API only. The number is used through the API | Full throughput, groups and calling available |

A profile can hold several WhatsApp numbers, each its own account. One number can be live on one profile only. If the user picks a number that conflicts, they come back to your `redirect_url` with an `error` such as `one_whatsapp_per_profile`, `whatsapp_number_pinned_to_profile` or `whatsapp_number_already_connected`. See [Connect WhatsApp](https://pozy.ai/docs/whatsapp) for each fix and for the full signup walkthrough.

## Sending messages

A new conversation starts with a template through [Create conversation](https://pozy.ai/docs/api/createInboxConversation). Replies inside the 24-hour window use [Send message](https://pozy.ai/docs/api/sendInboxMessage).

```bash cURL
curl -X POST https://api.pozy.ai/v1/inbox/conversations \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "participantId": "+13105551234",
    "templateName": "order_shipped",
    "templateLanguage": "en_US",
    "templateParams": ["Maria", "A-10423"]
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/inbox/conversations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    accountId: "66b2e19d8c3f5a7e9d0b1c2d",
    participantId: "+13105551234",
    templateName: "order_shipped",
    templateLanguage: "en_US",
    templateParams: ["Maria", "A-10423"],
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.data.conversationId);
```
```python Python
resp = requests.post(
    "https://api.pozy.ai/v1/inbox/conversations",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
        "participantId": "+13105551234",
        "templateName": "order_shipped",
        "templateLanguage": "en_US",
        "templateParams": ["Maria", "A-10423"],
    },
)
print(resp.json()["data"]["conversationId"])
```

Once the customer replies, send free-form messages to that `conversationId`:

```json
{
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "message": "Your parcel is with the courier. Tracking link below.",
  "attachmentUrl": "https://pozy.ai/media/label.pdf",
  "attachmentType": "file"
}
```

What a message can carry:

| Type | How |
| --- | --- |
| Text | `message`. Links get a preview unless `linkPreview: false` |
| Image, video, audio, document | `attachmentUrl` and `attachmentType` (`image`, `video`, `audio`, `file`) |
| Voice note | An OGG (Opus, mono) audio attachment with `voiceNote: true` |
| Template | `template` with `name`, `language` and `components`, or the `templateName` fields on create |
| Interactive | `interactive` with a `type` such as `list`, `cta_url`, `flow`, `location_request_message`, `voice_call`, `product`, `carousel` |

Send an `Idempotency-Key` header so a retry never sends twice. See [Idempotency](https://pozy.ai/docs/getting-started/idempotency).

## Media requirements

| Type | Formats | Max size |
| --- | --- | --- |
| Images | JPEG, PNG | 5 MB |
| Videos | MP4, 3GPP (H.264 video, AAC audio) | 16 MB |
| Documents | PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT | 100 MB |
| Audio | MP3, OGG (Opus), AMR, AAC | 16 MB |

Every URL must be public with no login and return the file, not a web page. Use [Media](https://pozy.ai/docs/publishing/media) to host files.

## Limits

| Limit | Value |
| --- | --- |
| Free-form messages | Only inside the 24-hour customer service window |
| New number tier | 250 unique contacts in 24 hours, raised by Meta over time |
| Messages to one recipient | About 10 a minute before Meta throttles that recipient |
| Coexistence throughput | 20 messages per second |
| Recipients | Must be on WhatsApp, written in E.164 (for example `+13105551234`) |

## Analytics

WhatsApp has no post analytics. Delivery is tracked per message and per broadcast recipient through the `message.delivered`, `message.read` and `message.failed` webhooks. Conversation volume and response times are in [Inbox analytics](https://pozy.ai/docs/analytics/inbox-analytics).

## Inbox

WhatsApp conversations live in the same [Inbox](https://pozy.ai/docs/inbox/messages) as every other channel. Contacts, custom fields and automations such as [workflows](https://pozy.ai/docs/automations/workflows) and [sequences](https://pozy.ai/docs/automations/sequences) work on WhatsApp. There are no comments.

## Pricing

WhatsApp has two billers:

- **Pozy** bills the connected account, outbound messages past the monthly free allowance, numbers you buy through Pozy, and the carrier leg of outbound calls.
- **Meta** bills template delivery and its per-minute calling fee straight to the WhatsApp Business Account. Meta needs a payment method on that account, or it stops delivering templates once the free tier runs out.

See [WhatsApp pricing](https://pozy.ai/docs/whatsapp/pricing) and [Pricing](https://pozy.ai/docs/billing).

## Errors you may see

| Error | Cause | Fix |
| --- | --- | --- |
| 131047 "Re-engagement message" | The 24-hour window closed | Send an approved template |
| 132001 "Template not found" | Name or language does not match | Use the exact name and language code (`en` is not `en_US`) |
| 131021 "Invalid phone number" | Malformed number or not on WhatsApp | Use E.164 with the country code |
| 131026 "Message undeliverable" | The recipient cannot receive it, or Meta is holding back marketing messages to them | Check the number. If other messages deliver, send it as a utility template |
| 131056 "Too many messages sent to this recipient" | More than about 10 a minute to one person | Pace sends per recipient |
| 131052 "Media download failed" | WhatsApp could not fetch the media URL | Serve the file from a public URL |
| 131031 "Account locked" | Meta suspended the WhatsApp Business Account | Contact Meta support |
| `(#200)` permission error on every send | The number has a two-step PIN that was not registered | [Register the number](https://pozy.ai/docs/api/registerWhatsAppNumber) with its PIN |

A message that was accepted and later fails arrives as `message.failed` with the same Meta code. [Errors](https://pozy.ai/docs/getting-started/errors) covers Pozy's error format.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/connect/{platform}`](https://pozy.ai/docs/api/getConnectUrl) | Get OAuth connect URL |
| [`POST /v1/connect/whatsapp/credentials`](https://pozy.ai/docs/api/connectWhatsAppCredentials) | Connect WhatsApp via credentials |
| [`GET /v1/connect/whatsapp/select-phone-number`](https://pozy.ai/docs/api/listWhatsAppPhoneNumbers) | List numbers for selection |
| [`POST /v1/connect/whatsapp/select-phone-number`](https://pozy.ai/docs/api/completeWhatsAppPhoneSelection) | Complete number selection |
| [`POST /v1/inbox/conversations`](https://pozy.ai/docs/api/createInboxConversation) | Create conversation |
| [`POST /v1/inbox/conversations/{conversationId}/messages`](https://pozy.ai/docs/api/sendInboxMessage) | Send message |
| [`GET /v1/whatsapp/templates`](https://pozy.ai/docs/api/getWhatsAppTemplates) | List templates |
| [`POST /v1/whatsapp/templates`](https://pozy.ai/docs/api/createWhatsAppTemplate) | Create template |
| [`POST /v1/broadcasts`](https://pozy.ai/docs/api/createBroadcast) | Create broadcast draft |
| [`POST /v1/broadcasts/{broadcastId}/send`](https://pozy.ai/docs/api/sendBroadcast) | Send broadcast now |
| [`GET /v1/whatsapp/business-profile`](https://pozy.ai/docs/api/getWhatsAppBusinessProfile) | Get business profile |
| [`POST /v1/whatsapp/business-profile`](https://pozy.ai/docs/api/updateWhatsAppBusinessProfile) | Update business profile |
| [`POST /v1/accounts/{accountId}/whatsapp/register`](https://pozy.ai/docs/api/registerWhatsAppNumber) | Register a connected WhatsApp number on the Cloud API |

The full list is in the [API reference](https://pozy.ai/docs/api#whatsapp).

## Webhook events

| Event | When it fires |
| --- | --- |
| [`message.received`](https://pozy.ai/docs/webhooks/events#message-received) | Message received. |
| [`conversation.started`](https://pozy.ai/docs/webhooks/events#conversation-started) | Conversation started. |
| [`message.sent`](https://pozy.ai/docs/webhooks/events#message-sent) | Message sent. |
| [`message.edited`](https://pozy.ai/docs/webhooks/events#message-edited) | Message edited. |
| [`message.deleted`](https://pozy.ai/docs/webhooks/events#message-deleted) | Message deleted. |
| [`message.delivered`](https://pozy.ai/docs/webhooks/events#message-delivered) | Message delivered. |
| [`message.read`](https://pozy.ai/docs/webhooks/events#message-read) | Message read. |
| [`message.played`](https://pozy.ai/docs/webhooks/events#message-played) | Message played. |
| [`message.failed`](https://pozy.ai/docs/webhooks/events#message-failed) | Message delivery failed. |
| [`whatsapp.template.status_updated`](https://pozy.ai/docs/webhooks/events#whatsapp-template-status-updated) | WhatsApp template status updated. |
| [`whatsapp.template.category_updated`](https://pozy.ai/docs/webhooks/events#whatsapp-template-category-updated) | WhatsApp template category updated. |
| [`whatsapp.account.quality_updated`](https://pozy.ai/docs/webhooks/events#whatsapp-account-quality-updated) | WhatsApp quality rating or messaging limit changed. |
| [`whatsapp.account.status_updated`](https://pozy.ai/docs/webhooks/events#whatsapp-account-status-updated) | WhatsApp Business Account restricted or reinstated. |
| [`whatsapp.account.alert_received`](https://pozy.ai/docs/webhooks/events#whatsapp-account-alert-received) | WhatsApp account alert received. |
| [`whatsapp.account.name_status_updated`](https://pozy.ai/docs/webhooks/events#whatsapp-account-name-status-updated) | WhatsApp display-name review outcome. |
