# Connect WhatsApp

Send your customer to PaalChat's hosted Meta signup and know when they are connected.

> **In a nutshell:** Ask for a one-time connect link, redirect the customer's admin to it, and wait for the whatsapp.connection callback. PaalChat runs Meta's Embedded Signup, verifies everything with Meta and keeps the credentials.

## How it works

1. **You ask for a link.** `POST /businesses/{external_id}/whatsapp/connect` returns a one-time URL.
2. **The admin opens it.** On PaalChat's page they log in to Meta in Meta's own window, choose or create the WhatsApp Business account and phone number, and approve access.
3. **PaalChat verifies with Meta.** It checks the grant, reads the account and its numbers, registers the number, subscribes webhooks and syncs templates.
4. **You are told.** A `whatsapp.connection` callback with `event: connected` arrives, and the admin can follow the link back to your app.

Your product never sees the Meta login or the Meta token.

## 1. Create the link

```bash
curl -X POST https://whatsapp.paaltech.org/api/v1/businesses/presec/whatsapp/connect \
  -H "Authorization: Bearer $PAALCHAT_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"return_url": "https://presec.skuuls.paaltech.org/settings/whatsapp"}'
```

```php
public function connect()
{
    $link = Http::withToken(config('services.paalchat.token'))->acceptJson()
        ->post('https://whatsapp.paaltech.org/api/v1/businesses/'.tenant('id').'/whatsapp/connect', [
            'return_url' => route('settings.whatsapp'),
        ])->throw()->json('data');

    return redirect()->away($link['url']);
}
```

```javascript
app.post('/settings/whatsapp/connect', async (req, res) => {
  const r = await fetch(`${BASE}/businesses/${req.shop.id}/whatsapp/connect`, {
    method: 'POST', headers: HEADERS,
    body: JSON.stringify({ return_url: 'https://pos.example.com/settings/whatsapp' }),
  });
  const { data } = await r.json();
  res.redirect(data.url);
});
```

```python
link = requests.post(f"{BASE}/businesses/{shop.id}/whatsapp/connect", headers=HEADERS,
    json={"return_url": "https://pos.example.com/settings/whatsapp"}).json()["data"]
return redirect(link["url"])
```

```json
{"data": {"url": "https://whatsapp.paaltech.org/connect/EsCRzWiltOSLzMBAvEyNHt4bdoWsdAYXwkPKiL2KT83aQk98NFn5coKqweclo4Ih", "expires_at": "2026-10-03T18:54:28+00:00"}}
```

> [!WARNING]
> Treat the link as a secret: whoever holds it can connect a WhatsApp account to
> this business. Send it only to the customer's administrator, and do not store it.

## Link rules

- Valid for **72 hours** and until it is used successfully. If the admin closes
  Meta's window, they can try again with the same link.
- Asking for a new link cancels older unused links for that business.
- `return_url` must be `https://` and on your product's **registered return
  origins** - ask PaalTech to register them (for example `https://*.skuuls.paaltech.org`
  for every school subdomain). Any other URL gets `422 return_url_not_allowed`, so the
  connect page can never send your customers somewhere else. After success the page
  offers a link back to it with `whatsapp=connected` added to the query.
- A suspended business gets `409 business_suspended`.

## 2. Wait for the result

The `whatsapp.connection` callback may arrive before or after the admin returns.
When they land on your `return_url`, read the state:

```http
GET /api/v1/businesses/presec/whatsapp
```

```json
{"data": {"status": "connected", "wabas": [{"waba_id": "102290129340398", "status": "connected",
  "phone_numbers": [{"phone_number_id": "106540352242922", "display_phone_number": "+233 30 200 0000", "verified_name": "Presec Legon"}]}]}}
```

Show the number and verified name on your settings page.

> [!NOTE]
> The business pays Meta for conversations directly. Remind the admin to add a
> payment method for the number in Meta's WhatsApp Manager.

## If setup fails

If a step fails at Meta, the WhatsApp account goes back to `pending` (you get a
`whatsapp.connection` callback with `event: setup_failed`) and `health.last_error`
says why, in Meta's words. The same link still works, so the admin can simply try
again. Reconnecting the same account updates it in place - history and messages are kept.

## Connection states

| State | Meaning | Can send? | What to do |
|---|---|---|---|
| `pending` | Not set up yet, or setup failed (`health.last_error` says why) | No | Offer a new link |
| `connecting` | Signup finished, setup at Meta in progress | No | Wait |
| `connected` | Ready | Yes | - |
| `degraded` | Working, with a warning - usually the access token expires within 7 days | Yes | Ask the admin to reconnect soon |
| `maintenance` | Paused by PaalChat operators; sends get `409 connection_paused` | No | Retry later; incoming messages still arrive |
| `suspended` | Meta disabled the account | No | The business resolves it with Meta; it comes back by itself |
| `revoked` | The credential stopped working, or the customer removed PaalChat's access in Meta | No | Offer a new link |
| `disconnected` | Disconnected on purpose, by you or an operator | No | Offer a new link if they want it back |

## Disconnect

When a customer leaves, disconnect each of their WhatsApp accounts:

```http
DELETE /api/v1/businesses/presec/whatsapp/wabas/102290129340398
```

PaalChat deletes its Meta token and stops sending. The number stays the customer's at Meta.

## Moving an account to another business

A WhatsApp account can move between two businesses on PaalChat (for example when a school's
campuses merge). PaalTech requests the move; each business approves or rejects it through its
product ([List account transfers](/docs/api/list-transfers), [Approve or reject a
transfer](/docs/api/decide-transfer)); after both approve it moves 24 hours later, with its
numbers and templates. Messages and conversations stay with the business they happened in.
