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

> ## Agent Instructions
> Base URL: https://api.social-api.ai/v1. Send your API key in the Authorization header: Authorization: Bearer <your API key>.
> Every error response from /v1 is JSON: { "error": { "code", "message", "meta" (optional) }, "request_id" }. Branch on error.code; use error.message only for logs.
> A 429 response carries a Retry-After header, in seconds, when the limit has a known wait. Authenticated responses can carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset for the per-minute request budget your SocialAPI account shares across every API key (see /guides/rate-limits).
> Paginated list endpoints return next_cursor, at the top level or inside pagination (see /guides/pagination). Pass it as the cursor query parameter to get the next page. An absent or empty next_cursor means you have the last page.
> Treat every ID as an opaque string: do not parse it or assume a format.
> Support differs by platform and by operation. Check /platforms/overview before assuming an operation works on a platform. An unsupported operation returns 501 with error.code resource.not_supported.

# WhatsApp

> Connect a WhatsApp Business phone number to receive and reply to direct messages through SocialAPI.

**Messaging connector.** WhatsApp is a DM-only connector: you connect a WhatsApp Business phone number through Meta's Embedded Signup, then use our unified inbox to receive and reply to the messages that number gets. There are no posts, comments, reviews, or mentions.

## At a glance

| Field | Value |
| - | - |
| Platform slug | `whatsapp` |
| Auth type | Embedded Signup (OAuth), Meta-hosted |
| API | WhatsApp Cloud API |
| Access | Live. Available on all plans, except that sending template messages needs a paid plan (see [Plans](https://docs.social-api.ai/guides/plans.md)) |
| Best for | A unified inbox for the messages your WhatsApp Business number receives. |

## Capabilities

| Feature | Supported | Notes |
| - | - | - |
| Receive DMs | Yes | Inbound messages arrive in real time via webhooks |
| Free-form reply | Yes | Text, media, or interactive, and only inside the open 24-hour service window (see below) |
| Template messages | Yes | Create, list, edit, delete, and validate templates via the API, and send approved templates (including outside the 24-hour window). Sending a template needs a paid plan: on the Free plan, `send-template` returns `403 whatsapp.paid_plan_required`. See [Plans](https://docs.social-api.ai/guides/plans.md) |
| Interactive messages | Yes | Up to 3 reply buttons, or a list of up to 10 rows. Taps arrive on your webhook as `dm.received` |
| Conversation list / history | Partial | There is no history API, so the inbox is built from webhooks received after you connect. A number connected from the WhatsApp Business app also brings the chat history the business shares (see [Keep using the WhatsApp Business app](#keep-using-the-whatsapp-business-app)) |
| Phone-number management | Yes | Request a code, verify, register, deregister, and check status of the business number |
| Business profile | Yes | Read and update the WhatsApp business profile |
| Publishing (posts) | No | WhatsApp has no posts concept |
| Comments / reviews / mentions | No | Not applicable |

## What you need

* A **WhatsApp Business phone number** that is not registered on the consumer WhatsApp app. A number the business uses in the WhatsApp Business app can keep running there, see [Keep using the WhatsApp Business app](#keep-using-the-whatsapp-business-app). During Embedded Signup you can select an existing number or create one.
* **Admin access to a Meta Business** (business.facebook.com).

You do **not** need to submit your own Meta App Review or complete business verification to connect. We are the Tech Provider, so the app review and provider-side setup are handled on our side. The person connecting signs in with their Meta Business account, picks their WhatsApp Business Account and phone number, and the number is connected.

## Connecting

WhatsApp connects like every other platform with a consent screen: your backend asks for an `auth_url`, you send your customer there, and they come back to your `redirect_uri` with the connected account. Meta's signup runs on Meta's own pages, so there is nothing to embed or host on your side and no domain to register: it works from any domain. Your API key stays on your backend the whole time. (You can also connect from our dashboard.)

### 1. Get the signup link (your backend)

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/accounts/connect \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "whatsapp",
    "redirect_uri": "https://app.example.com/oauth/callback",
    "state": "session_abc123"
  }'
```

Response (HTTP 202):

```json theme={null}
{
  "auth_url": "https://www.facebook.com/v26.0/dialog/oauth?client_id=...",
  "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2",
  "message": "Open this link to connect your whatsapp account. Once you've authorized, the account will be connected automatically."
}
```

Always set `redirect_uri`: it is where your customer lands with the result, and the only way to receive the number choice described below. See [Redirect URI rules](https://docs.social-api.ai/guides/connect-accounts.md#redirect-uri-rules). `state` is optional and comes back unchanged on the redirect.

`onboarding` picks the signup Meta shows:

| `onboarding` | Meta shows | Result |
| - | - | - |
| `cloud_api` (default) | The WhatsApp Business Account and phone number picker | The number is used through the API only. Use it for a new number or one already on the Cloud API. |
| `business_app` | "Connect your existing WhatsApp Business app" | The number keeps working in the WhatsApp Business app too. See [Keep using the WhatsApp Business app](#keep-using-the-whatsapp-business-app). |

### 2. Send your customer to `auth_url` (browser)

Redirect the browser to `auth_url`, or open it in a new tab. Your customer signs in with Facebook, picks or creates their business portfolio, WhatsApp Business Account and phone number, and verifies the number if it is new.

### 3. Read the result on your `redirect_uri`

On success, your customer lands on your `redirect_uri` with the account appended:

```
https://app.example.com/oauth/callback?account_id=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&platform=whatsapp&state=session_abc123&status=success
```

`account_id` is the ID every WhatsApp call takes from here on. The number's messages now flow into your inbox.

On failure, the redirect carries `status=error` with `error` and `error_description`. The WhatsApp-specific values are:

| `error` | Meaning |
| - | - |
| `whatsapp.no_waba` | No WhatsApp Business Account was shared during signup. Start a new connection. |
| `whatsapp.no_phone_number` | The WhatsApp Business Account has no phone number yet. Add one in Meta Business Manager, then connect again. |
| `whatsapp.number_banned` | Meta has banned this number. Connecting again does not help until Meta lifts the ban. |

See [Redirect parameters](https://docs.social-api.ai/guides/connect-accounts.md#redirect-parameters) for the full list.

### Choose a number

When the WhatsApp Business Account holds more than one phone number, the redirect carries `status=selection_required` and a `connection_id` instead of an `account_id`. Read the numbers with `GET /v1/accounts/pending/{connection_id}`:

```bash theme={null}
curl https://api.social-api.ai/v1/accounts/pending/pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2 \
  -H "Authorization: Bearer $SOCIALAPI_KEY"
```

```json theme={null}
{
  "connection_id": "pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2",
  "platform": "whatsapp",
  "expires_at": "2026-10-26T12:30:00Z",
  "profiles": [
    { "platform_account_id": "106540352242922", "display_name": "Acme Support", "username": "+1 555-010-0199", "profile_picture_url": "", "bio": "" },
    { "platform_account_id": "106540352242923", "display_name": "Acme Sales", "username": "+1 555-010-0200", "profile_picture_url": "", "bio": "" }
  ]
}
```

Each entry in `profiles` is one number: `platform_account_id` is its phone number ID, `display_name` the verified business name, and `username` the phone number. Connect one with `POST /v1/accounts/pending/{connection_id}/select`:

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/accounts/pending/pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2/select \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "platform_account_id": "106540352242922" }'
```

The response (HTTP 201) is the connected account. Choose within 30 minutes; after that the connection expires and returns 404, and your customer runs the signup again. Only the number you choose is connected.

### Keep using the WhatsApp Business app

To connect a number the business already uses in the WhatsApp Business app, and keep using it there, send `"onboarding": "business_app"` in step 1. Meta then walks your customer through linking the app, including scanning a QR code with their phone. The phone needs WhatsApp Business app 2.24.17 or later, and Meta decides during signup whether the number is eligible.

Once the number is connected:

* The chat history the business chooses to share in the app (up to six months) appears in its conversations as WhatsApp delivers it. Imported messages raise no webhook events, and their attachments carry `attachment_type` without an `attachment_url`.
* Messages the business sends from the phone app appear in the conversation and raise `dm.sent`, like messages sent through the API.
* Sending is capped at 20 messages per second, Meta's fixed limit for these numbers.
* The number is already registered, so you never call `register` for it. `status` reports it as registered, and `register` refuses it with `409 whatsapp.business_app_number`.
* The `deregister` endpoint does not work for these numbers. The business disconnects the number from the WhatsApp Business app's settings.

## The 24-hour service window

WhatsApp only lets a business send free-form messages within **24 hours** of the customer's most recent message. Each new inbound message from that customer resets the 24-hour timer.

* **Inside the window:** you can send a free-form text reply from the inbox.
* **Outside the window:** free-form replies are rejected with `whatsapp.window_closed`. To reach the customer, send an approved message template with `send-template` (see [Message templates](#message-templates) below), which is allowed at any time.

## Message templates

Templates are pre-approved messages you can send outside the 24-hour window (order updates, authentication codes, and the like). You create, manage, and send them entirely through the API, scoped to a connected account.

### Create a template

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/platforms/whatsapp/accounts/{account_id}/templates \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_confirmation",
    "category": "UTILITY",
    "language": "en_US",
    "components": [
      {
        "type": "BODY",
        "text": "Hello {{1}}, your order {{2}} is confirmed.",
        "example": { "body_text": [["Jordan", "A1234"]] }
      }
    ]
  }'
```

A new template starts in `PENDING`; Meta reviews it and the status moves to `APPROVED` or `REJECTED` asynchronously. `category` is one of `UTILITY`, `MARKETING`, or `AUTHENTICATION` (marketing content in a `UTILITY` template is auto-rejected). Call `POST /v1/platforms/whatsapp/templates/validate` first to catch field-level problems before you submit.

### Send a template

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/platforms/whatsapp/accounts/{account_id}/send-template \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "15551234567",
    "template_name": "order_confirmation",
    "language": "en_US",
    "components": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "Jordan" },
          { "type": "text", "text": "A1234" }
        ]
      }
    ]
  }'
```

`to` is E.164 without the leading `+`. The template must already be `APPROVED` and `language` must match an approved variant. This send is allowed whether or not the 24-hour window is open, and the message is recorded in the inbox thread. On the Free plan the request returns `403 whatsapp.paid_plan_required` and nothing is sent. Replies to people who messaged you work on every plan. See [Plans](https://docs.social-api.ai/guides/plans.md).

The response includes `message_id`, the SocialAPI message ID for the send, alongside WhatsApp's own `messages[].id` (the `wamid`). `message_id` is the same value that arrives as `id` on the `dm.status.sent`, `dm.status.delivered`, `dm.status.read`, and `dm.status.failed` webhooks for this message, so you can bind a delivery receipt to the row you created without matching on the `wamid`.

Listing, editing, and deleting templates, plus phone-number management (`request-code`, `verify-code`, `register`, `deregister`, `status`) and the business profile, are also available. See the [API reference](https://docs.social-api.ai/api-reference/introduction.md) for the full set of `/v1/platforms/whatsapp` endpoints and their schemas.

## Receiving and replying

WhatsApp has no message-history API, so the inbox covers messages from connect time onward. The exception is a number connected from the WhatsApp Business app, which also brings the chat history the business chooses to share (see [Keep using the WhatsApp Business app](#keep-using-the-whatsapp-business-app)).

### List DM conversations

```bash theme={null}
curl "https://api.social-api.ai/v1/inbox/conversations?account_id={id}" \
  -H "Authorization: Bearer $SOCIALAPI_KEY"
```

### Reply to a message

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/inbox/conversations/{conversation_id}/messages \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "{account_id}",
    "text": "Thanks for reaching out!"
  }'
```

`account_id` is required. The reply only succeeds while the 24-hour service window is open.

### Sending media

Attach media with the [`attachments` array](https://docs.social-api.ai/guides/direct-messages#attachments), either a `url` WhatsApp can fetch or a `media_id` from your media library:

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/inbox/conversations/{conversation_id}/messages \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "{account_id}",
    "attachments": [
      { "source_type": "media_id", "source": "{media_id}", "caption": "The damaged panel" }
    ]
  }'
```

WhatsApp has no generic attachment type, so `file` and anything we cannot classify are sent as a document, with the filename taken from the URL's last path segment. A `caption` is supported on image, video, and document; on audio it is rejected, since WhatsApp has no caption there.

A `url` attachment is fetched by WhatsApp's own servers, so it must be publicly reachable without authentication (a `media_id` always is). WhatsApp's limits apply: images 5 MB (JPEG, PNG), video 16 MB (MP4, 3GPP), audio 16 MB (AAC, AMR, MP3, M4A, OGG), documents 100 MB. A file it cannot fetch, or one over a limit, returns `whatsapp.media_download_failed`.

`source_type: "platform_attachment_id"` is not supported on WhatsApp.

### Interactive messages

Send up to 3 reply buttons, or a list of up to 10 rows, so the recipient answers by tapping instead of typing. Both need `text`, which becomes the message body.

Every button carries a `payload` (a row carries an `id`) that you choose. WhatsApp never interprets it: it is echoed back to you untouched when the recipient taps, so use it as your own routing key.

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/inbox/conversations/{conversation_id}/messages \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "{account_id}",
    "text": "Your weekly digest is ready. What next?",
    "buttons": [
      { "type": "postback", "title": "Review drafts", "payload": "menu:review_drafts" },
      { "type": "postback", "title": "Skip this week", "payload": "menu:skip" }
    ]
  }'
```

For more than 3 options, send a list instead. `list_button` is the label on the button that opens it:

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/inbox/conversations/{conversation_id}/messages \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "{account_id}",
    "text": "Which location?",
    "list_button": "View locations",
    "list_sections": [
      {
        "title": "Your locations",
        "rows": [
          { "id": "loc:1", "title": "Downtown", "description": "3 reviews waiting" },
          { "id": "loc:2", "title": "Airport" }
        ]
      }
    ]
  }'
```

Limits: button titles 20 characters, row titles 24, row descriptions 72, `list_button` 20, and 10 rows in total across at most 10 sections. Payloads and row ids must be unique within a message, since a tap echoes only the id. Buttons and `list_sections` are mutually exclusive, and neither can be combined with an attachment.

`quick_replies` and `cards` are Instagram and Facebook only; sending them to WhatsApp returns a validation error rather than silently dropping them. A url button is available on [templates](#message-templates), not on interactive messages.

Interactive messages are free-form, so they are subject to the 24-hour service window. To open a menu outside it, send an approved template with `QUICK_REPLY` buttons instead; taps on those reach your webhook the same way.

### Receiving taps

A tap is delivered as an ordinary `dm.received` event, not a separate event type: on WhatsApp the tapped title is posted into the conversation as the contact's own message, and it opens the 24-hour window like any inbound message. The payload you set rides on the event's metadata:

```json theme={null}
{
  "content": { "text": "Review drafts" },
  "metadata": {
    "platform_id": "wamid.xxx",
    "interactive_type": "button_reply",
    "payload": "menu:review_drafts"
  }
}
```

`interactive_type` is `button_reply` (a reply button), `list_reply` (a list row), or `button` (a template quick reply). Both fields are absent on an ordinary message, so switch on `metadata.payload` when it is present and treat the message as text otherwise.

### Inbound media

WhatsApp exposes inbound media only to the connected account, never to an external caller, so it cannot be linked directly. For WhatsApp (and Telegram), `messages.attachment_url` is a SocialAPI URL (`/v1/inbox/media/{id}`) that you retrieve with your API key, unlike the other connectors where `attachment_url` is the platform's own URL. See [Inbox media](https://docs.social-api.ai/guides/inbox.md#fetch-an-inbound-attachment) for the endpoint and its response codes.

## Errors

| HTTP | Code | When it occurs |
| - | - | - |
| 400 | `whatsapp.window_closed` | The 24-hour service window has closed. Free-form replies are no longer allowed; send an approved template with `send-template` instead. |
| 404 | `whatsapp.template_not_found` | The named template does not exist on this account's WABA. |
| 400 | `whatsapp.template_paused` | The template is paused for quality reasons and cannot be sent. |
| 400 | `whatsapp.media_download_failed` | WhatsApp could not download the attachment. The URL is unreachable, the format is unsupported, or the file exceeds the size limit for its type. |
| 400 | `whatsapp.media_upload_failed` | WhatsApp rejected the media file. |
| 400 | `validation.caption_unsupported` | A caption was set on an audio attachment. WhatsApp audio messages have no caption. |
| 400 | `whatsapp.undeliverable` | The recipient is not a WhatsApp user. |
| 403 | `whatsapp.paid_plan_required` | A template was sent on the Free plan. Template sends need a paid plan: see [Plans](https://docs.social-api.ai/guides/plans.md). Replies inside the 24-hour window work on every plan. |
| 403 | `whatsapp.user_opted_out` | The recipient has opted out of messages from this business. |
| 409 | `whatsapp.business_app_number` | `register` was called for a number that stays on the WhatsApp Business app. It is already registered: do not retry. |
| 429 | `whatsapp.frequency_cap` | The recipient has hit their daily marketing-message limit. |
| 429 | `whatsapp.pair_rate_limit` | Too many messages sent to this same recipient. |
| 429 | `whatsapp.throughput_exceeded` | The number's messaging throughput limit was exceeded. |
| 401 | `platform.whatsapp.auth` | The connection is no longer authorized. Reconnect the account. |

See [Errors](https://docs.social-api.ai/guides/errors.md) for the full catalog.

## Related

* [Supported platforms](https://docs.social-api.ai/platforms/overview.md)
* [Inbox](https://docs.social-api.ai/guides/inbox.md)
* [Errors](https://docs.social-api.ai/guides/errors.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.