> ## 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.
> 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](/guides/plans)) |
| 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](/guides/plans) |
| 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 uses Meta's Embedded Signup, a three-leg flow that you drive from your own backend and frontend with your API key. Your customer only ever interacts with Meta's hosted popup; your API key stays server-side the whole time. (You can also connect from our dashboard, which runs this same flow for you.)

### Leg 1: start the connection (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" }'
```

Response (HTTP 202):

```json theme={null}
{
  "auth_url": "",
  "state": "<CSRF token, bound to your API key, 10-minute TTL>",
  "metadata": {
    "app_id": "...",
    "config_id": "...",
    "solution_id": "..."
  }
}
```

The `metadata` is exactly what Meta's JavaScript SDK needs to launch the popup. Pass `state` through to your frontend, hold onto it for leg 3.

`solution_id` can come back empty. That is expected: it is optional, and when it is empty you omit the `extras` object from the `FB.login` call in leg 2. The rest of the flow is unchanged.

### Leg 2: launch the Meta popup (browser)

Load Facebook's JavaScript SDK and call `FB.login` with the `config_id` and `solution_id` from leg 1:

```javascript theme={null}
FB.login(
  (response) => {
    const code = response.authResponse?.code;
    // send code (plus waba_id + phone_number_id from the event below) to your backend
  },
  {
    config_id: CONFIG_ID,
    response_type: "code",
    override_default_response_type: true,
    extras: { setup: { solutionID: SOLUTION_ID } },
  },
);
```

As the customer completes the popup, Meta fires the `WA_EMBEDDED_SIGNUP` message event carrying their `waba_id` and `phone_number_id`. Capture the `code` and both IDs, and send them to your backend. See Meta's [Embedded Signup](https://developers.facebook.com/docs/whatsapp/embedded-signup) docs for the exact SDK and event wiring.

### Leg 3: complete the connection (your backend)

```bash theme={null}
curl -X POST https://api.social-api.ai/v1/oauth/exchange \
  -H "Authorization: Bearer $SOCIALAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "whatsapp",
    "code": "<from the popup>",
    "metadata": {
      "state": "<from leg 1>",
      "waba_id": "<from the popup>",
      "phone_number_id": "<from the popup>"
    }
  }'
```

We validate `state` against the one we issued to your API key, exchange the `code` for the customer's token, store the account, and return HTTP 201 with the connected account. The number's messages now flow into your inbox.

### Keep using the WhatsApp Business app

To connect a number the business already uses in the WhatsApp Business app, and keep using it there, add `featureType` and `sessionInfoVersion` to `extras` in leg 2:

```javascript theme={null}
extras: {
  setup: { solutionID: SOLUTION_ID },
  featureType: "whatsapp_business_app_onboarding",
  sessionInfoVersion: "3",
},
```

Without `featureType`, Meta rejects such a number at the phone-number step with error #2655115 ("this account type does not support partner sharing"). The phone needs WhatsApp Business app 2.24.17 or later, and Meta decides at that step whether the number is eligible.

This flow ends with the `FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING` event, which carries `waba_id` but no `phone_number_id`. In leg 3, send `waba_id` and leave `phone_number_id` out. If the WhatsApp Business account holds more than one number, the exchange fails with `whatsapp.ambiguous_phone_number`, and you pass `phone_number_id` explicitly.

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.

### Registering your domain

The popup runs against our Meta app, and Meta's Login-for-Business SDK enforces an allowlist of domains that are permitted to launch it. To run `FB.login` from your own domain (for example, `app.yourcompany.com`), we have to register that domain in our Meta app first, so contact us to add it.

If you would rather not host the popup yourself, connect from our dashboard instead. It runs the flow on our domain and works for any customer without a per-domain step.

## 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](/guides/plans).

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](/api-reference/introduction) 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](/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](/guides/inbox#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](/guides/plans). 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](/guides/errors) for the full catalog.

## Related

* [Supported platforms](/platforms/overview)
* [Inbox](/guides/inbox)
* [Errors](/guides/errors)


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