---
title: "WhatsApp"
description: "Use the WhatsApp Cloud API for MainSDK notifications and WhatsApp sign-in - Meta business setup, templates, tokens, webhooks, main.yaml and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/whatsapp"
updated: "2026-10-28"
---

# WhatsApp

> Use the WhatsApp Cloud API for MainSDK notifications and WhatsApp sign-in - Meta business setup, templates, tokens, webhooks, main.yaml and swapping.

Canonical: https://mainsdk.dev/docs/integrations/whatsapp · Updated: 2026-10-28

The WhatsApp Cloud API is Meta's hosted API for WhatsApp Business. In a MainSDK app it is the default WhatsApp channel driver for [`notify`](https://mainsdk.dev/docs/interfaces/notify), so an order update or a payment reminder can reach a person on WhatsApp, and it is the default sender for WhatsApp one-time codes in [`auth`](https://mainsdk.dev/docs/interfaces/auth). Pick it when your users live on WhatsApp, as many do in India, and you want to connect to Meta directly rather than through a messaging provider such as Bird or MSG91.

## Interfaces it serves

### Notifications (`notify`)

- Status: Building for 0.3 · ships as a stable API
- Driver: `whatsapp-cloud` (WhatsApp Cloud API)
- Docs: https://mainsdk.dev/docs/interfaces/notify.md

```yaml
notify:
  driver: whatsapp-cloud
```

Swap to another provider: `bird`, `msg91`, `twilio`, `local`, `fcm`, `apns`, `expo`, `resend`, `ses`, `onesignal`, `slack`, `pagerduty`, `opsgenie`, `webhook`.

## Before you start

You need a Meta business account and a phone number that is not already registered on another WhatsApp account.

1. **Set up WhatsApp Business.** In Meta's developer tools, create an app with the WhatsApp product. It links to a WhatsApp Business Account owned by your business. Meta may ask you to verify the business before you can send at volume or display your business name.
2. **Add a phone number.** Register the number your app sends from in the WhatsApp Business Account and note its phone number ID. Meta also gives you a test number for development, which can send to a few recipients you register.
3. **Create message templates.** Business-initiated messages, sent outside the window that opens when a person messages you, must use a template Meta has approved. Create one template per notification (for example `order_shipped`), in each language you send, and wait for approval. Sign-in codes use a template in Meta's authentication category.
4. **Create an access token.** The temporary token Meta shows during setup expires quickly. For stage and production, create a long-lived token for a system user with access to the WhatsApp Business Account.
5. **Webhook (optional).** Meta sends delivery status and incoming messages to a webhook URL. The `notify` WhatsApp driver does not read them: it records the provider's message id when Meta accepts the message, and nothing after.

Store the token in your secrets manager, never in code or a committed env file.

## Configure

For notifications, name the driver on the `whatsapp` channel:

```yaml title="main.yaml"
notify:
  channels: [whatsapp, sms, email]
  whatsapp: whatsapp-cloud
  whatsapp-cloud:
    phone-number-id: "1234567890"       # the number your app sends from
    token: secret:WHATSAPP_TOKEN        # a system user's access token; this is the default name
    version: v21.0                      # the Graph API version; this is the default
  templates:
    order.shipped:
      whatsapp: { name: order_shipped, language: en, params: [orderId] }
```

- `name` is the template name Meta approved. `language` is its language code (default `en`; a `locale` on the call or the person overrides it). `params` lists the `data` keys, in order, that fill the template's body variables.
- A notification with no `whatsapp` template is skipped on WhatsApp with the reason `no-template`.
- A literal token fails validation with `MAIN-CONFIG-0004`.

> **Warning:** The WhatsApp Cloud API request shape is written from Meta's documentation and is unverified against the live API. Verify before production: send one template message to a recipient registered on Meta's test number.

### What the driver sends

Each message is one `POST https://graph.facebook.com/<version>/<phone-number-id>/messages` with `Authorization: Bearer <token>`. The JSON body holds `messaging_product: "whatsapp"`, `to` (digits, no plus) and `type: "template"` with the template name, the language code and the body parameters in order. The `id` Meta returns becomes the delivery's `providerId`. The call goes through MainSDK's network layer, is never retried by it, does not follow redirects, and reads at most 64 KiB of the answer. A 429, a 5xx or no answer is a retryable `MAIN-NOTIFY-0007` and `notify` tries the next channel.

For sign-in, add `whatsapp` to the methods and pick the sender:

```yaml title="main.yaml"
auth:
  driver: github
  methods: [passkeys, whatsapp]
  whatsapp: { driver: whatsapp-cloud }
```

India users usually expect WhatsApp first. Put it first in the market overlay:

```yaml title="main.in.yaml"
notify:
  channels: [whatsapp, sms, push, email]
auth:
  methods: [whatsapp, passkeys]
```

## Use it

Notify a person. WhatsApp is used when it is the first channel they allow and they have recorded consent; the template name maps to your approved template.

```ts title="app/orders/ship.ts"
import { notify } from "mainsdk";

export async function orderShipped(user: { id: string }, orderId: string) {
  return notify(user, {
    template: "order.shipped",
    data: { orderId },
    category: "orders",
    idempotencyKey: `order.shipped:${orderId}`,
  });
}
```

Sign in with a WhatsApp one-time code:

```ts title="app/sign-in/whatsapp/actions.ts"
"use server";
import { auth } from "mainsdk";

export async function sendCode(phone: string) {
  const { challengeId } = await auth.whatsapp.send(phone);
  return challengeId;
}

export async function confirmCode(challengeId: string, code: string) {
  return auth.whatsapp.verify(challengeId, code);   // MAIN-AUTH-0006 on reuse, expiry or too many tries
}
```

The MainSDK template name (`order.shipped`) maps to the Meta template name and language through `notify.templates.<name>.whatsapp`.

## Local development and testing

- **No Meta account:** with `notify.whatsapp: local`, `mainsdk dev` logs one line per WhatsApp message with the number masked.
- **Meta test number:** send real messages to the few recipients you register on it, with a temporary token, before your own number and templates are approved.
- **Tests:** `createTestApp()` from `@mainsdk/testing` runs `notify` on a local channel when `notify.whatsapp` is `local`; read it with `channels(app).whatsapp`. It enforces consent, opt-outs and quiet hours. `channels(app).whatsapp?.failNext(1)` simulates an outage, and `fakeMessagingServer("whatsapp-cloud")` records the request the real driver makes.

## What MainSDK adds on top

- WhatsApp only to recipients with recorded consent; without it the channel is skipped with `MAIN-NOTIFY-0004` and the next channel is tried.
- Business-initiated messages only with provider-approved templates.
- Opt-outs and quiet hours respected, in the person's time zone.
- Sign-in codes single use, expiring after 5 minutes, closed after 5 wrong tries, and rate limited per phone and per IP.
- A retry with the same idempotency key never sends twice.

See [Notifications](https://mainsdk.dev/docs/interfaces/notify) and [Sign-in and sessions](https://mainsdk.dev/docs/interfaces/auth).

## Limits and notes

- The WhatsApp channel driver is built (spec `email-notify`). The `auth.whatsapp` sender comes with the auth spec.
- The driver declares its own host, `graph.facebook.com`, so `network.allow` does not change.
- Set `notify.limits.whatsapp-per-day` to bound cost; more is skipped with `MAIN-NOTIFY-0010`.
- Meta limits how many people a new number can message in a day and raises the limit as quality holds. Plan rollouts with `notify.rollout` stages.
- Phone numbers are personal data. Label the fields that store them `personal` in [`db`](https://mainsdk.dev/docs/interfaces/db).
- Consent records are kept in your app's `kv` store, per phone number, as a keyed hash. Record consent with `notify.consent.create(user, "whatsapp")`.

## Swap to or from it

```yaml title="main.yaml"
notify:
  whatsapp: bird   # was whatsapp-cloud
auth:
  whatsapp: { driver: bird }   # was whatsapp-cloud
```

Your `notify()` and `auth.whatsapp` calls stay the same, and the WhatsApp conformance cases pass for WhatsApp Cloud API, Bird and MSG91. Add the `notify.bird` block with it (see [Bird](https://mainsdk.dev/docs/integrations/bird)).

> **Warning:** Sending is stateless, but your sending number and approved templates belong to your WhatsApp Business Account. Before switching, check that the new provider can send from the same number with the same templates, or register and approve them there first. Otherwise sends fail until approval comes through.

## FAQ

**Can I send any text on WhatsApp?** Not as the first message. Business-initiated messages must use an approved template, and MainSDK sends only to people with recorded consent.

**Do I need a separate setup for sign-in codes?** The same WhatsApp Business Account works for both. Sign-in codes need an approved template in the authentication category.

**WhatsApp Cloud API or Bird or MSG91?** All three pass the same conformance cases. Use the Cloud API to deal with Meta directly; use Bird or MSG91 if you already send SMS through them.

## Next

- [Notifications](https://mainsdk.dev/docs/interfaces/notify) and [Sign-in and sessions](https://mainsdk.dev/docs/interfaces/auth)
- [Bird](https://mainsdk.dev/docs/integrations/bird), [MSG91](https://mainsdk.dev/docs/integrations/msg91) and [Twilio](https://mainsdk.dev/docs/integrations/twilio)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
