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

# Run WhatsApp for your users

> Use Wassist as a managed WhatsApp platform: onboard your own customers' WhatsApp Business accounts, then handle their conversations with webhooks or managed agents.

If you're building a product for other businesses (a CRM, a booking tool, a helpdesk, an agency dashboard), you can offer WhatsApp inside it without becoming a Meta Tech Provider yourself. Your users connect their own WhatsApp Business account through a hosted signup link, the account lands in **your** Wassist organization, and you decide how each number is answered: by your own code through a webhook, or by a managed agent you create for that user.

Your users never need a Wassist login. Everything happens through your API key.

```mermaid theme={null}
flowchart LR
    U[Your user] -->|1. signup link| L[Wassist hosted page + Meta signup]
    L -->|2. account and numbers| O[Your Wassist organization]
    O -->|3a. routing: webhook| WH[Your webhook]
    O -->|3b. routing: agent| A[Agent you created for that user]
    C[Their WhatsApp customers] <--> O
```

## How it maps to your product

| Wassist                       | In your product                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------ |
| Your organization and API key | Your platform. One key manages every user's accounts.                                |
| Link session                  | One attempt by one of your users to connect WhatsApp                                 |
| WhatsApp account              | Your user's WhatsApp Business Account. They own it; Wassist is granted access to it. |
| Phone number                  | A number on your user's account. Each one routes to a webhook or an agent.           |
| Webhook                       | One endpoint that receives messages for all of your users                            |
| Agent                         | Usually one per user, with their prompt and tools                                    |

Wassist has no concept of your users, so keep the mapping yourself: your user ID, their WhatsApp account `id`, and their phone numbers.

<Note>
  Registering numbers needs the **Starter** plan or above on your organization, and the [Business API proxy](/api-reference/whatsapp-account/proxy/get) needs **Pro**.
</Note>

## 1. Send your user through signup

Create an API key under **Settings → Developers → API keys**, then create a link session when your user clicks "Connect WhatsApp" and redirect them to its `linkUrl`:

```js theme={null}
import { Wassist } from "@wassist/sdk";

const wassist = new Wassist({ apiKey: process.env.WASSIST_API_KEY });

app.post("/whatsapp/connect", async (req, res) => {
  const tenant = req.user.tenant;

  // Snapshot the accounts you already have, so you can spot the new one later
  const knownAccountIds = (await wassist.whatsappAccounts.list().all()).map((a) => a.id);

  const session = await wassist.whatsappLinkSessions.create({
    successUrl: `https://app.example.com/whatsapp/connected/${tenant.id}`,
    returnUrl: "https://app.example.com/settings/whatsapp",
  });

  await db.linkSessions.insert({ id: session.id, tenantId: tenant.id, knownAccountIds });
  res.redirect(session.linkUrl);
});
```

Your user lands on a Wassist-hosted page and picks one of three options, each of which opens Meta's signup popup:

* **Connect Existing App**: move a number from the WhatsApp Business app. Their contacts and chat history start syncing.
* **Create Fresh Number**: create a new WhatsApp Business Account through Meta.
* **Connect Existing WhatsApp Business API**: share an account that already uses the API.

When they finish, Wassist redirects them to your `successUrl` with `?session_id=<id>&confirmed=true` appended. The **Back** link on the hosted page goes to your `returnUrl`.

<Warning>
  Don't put a query string in `successUrl`. Wassist appends `?session_id=...` to it, so put your own identifiers in the path, as above.
</Warning>

Sessions stay usable until they succeed. If your user abandons one, or you issue them a new link, expire the old one with `wassist.whatsappLinkSessions.expire(sessionId)`.

## 2. Record the new account

On the success redirect, confirm the session with the API rather than trusting the query string, then find the account that appeared:

```js theme={null}
app.get("/whatsapp/connected/:tenantId", async (req, res) => {
  const pending = await db.linkSessions.get(req.query.session_id);
  if (!pending || pending.tenantId !== req.params.tenantId) return res.sendStatus(404);

  const session = await wassist.whatsappLinkSessions.get(pending.id);
  if (session.status !== "SUCCESS") return res.redirect("/settings/whatsapp?error=link_failed");

  const accounts = await wassist.whatsappAccounts.list().all();
  const account = accounts.find((a) => !pending.knownAccountIds.includes(a.id));

  await db.tenants.update(pending.tenantId, { whatsappAccountId: account.id });
  await registerNumbers(account.id); // step 3
  res.redirect("/settings/whatsapp?connected=1");
});
```

The link session doesn't return the account it created, which is why the snapshot above is needed. Two edge cases:

* **No new account appears.** The user re-linked an account that's already in your organization. Wassist refreshes its access token and keeps the same account `id`.
* **More than one new account appears.** Two of your users finished at the same moment. Match on the account's `name` (the business name they entered at Meta), or ask the user to pick.

## 3. Register the numbers

The **Connect Existing App** option registers the number automatically. For the other two, the account arrives with its numbers still on Meta's side, and you register the ones you want Wassist to handle. These two endpoints aren't in the SDK yet, so call them directly:

```js theme={null}
async function api(path, init = {}) {
  const res = await fetch(`https://backend.wassist.app/api/v1${path}`, {
    ...init,
    headers: {
      "X-API-Key": process.env.WASSIST_API_KEY,
      "Content-Type": "application/json",
    },
  });
  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  return res.json();
}

async function registerNumbers(accountId) {
  // Numbers on the account at Meta, flagged with whether Wassist already handles them
  const { data } = await api(`/whatsapp-accounts/${accountId}/phone-numbers/`);

  for (const number of data.filter((n) => !n.isLinkedToWassist)) {
    await api(`/whatsapp-accounts/${accountId}/manage/`, {
      method: "POST",
      body: JSON.stringify({ phoneNumber: number.display_phone_number }),
    });
  }

  const account = await wassist.whatsappAccounts.get(accountId);
  return account.phoneNumbers; // [{ id, number, bot }]
}
```

`number` on each registered phone number is the E.164 number without the `+` (for example `447700900100`). It's the identifier for every `phoneNumbers.*` call and the `whatsappNumber` field on webhook events, so store it against your user.

<Accordion title="User doesn't have a number? Give them a Wassist UK line" icon="phone">
  Add a pre-verified Wassist number to their account. It's ready straight away, with no SMS verification:

  ```js theme={null}
  const { available_numbers } = await wassist.whatsappAccounts.availableNumbers();

  await wassist.whatsappAccounts.addNumber(account.id, {
    id: available_numbers[0].id,
    name: tenant.businessName, // the display name Meta shows to customers
  });
  ```
</Accordion>

## 4. Handle their conversations

Pick per number. You can run some users on webhooks and others on managed agents, or mix both on one number.

<Tabs>
  <Tab title="Webhook: your code replies">
    Create **one** webhook for your whole platform under [Settings → Developers → Webhooks](https://wassist.app/settings/developers/webhooks), then route each of your users' numbers to it:

    ```js theme={null}
    await wassist.phoneNumbers.subscribe(number, {
      webhookId: process.env.WASSIST_WEBHOOK_ID,
      applyToExisting: true,
    });
    ```

    Every inbound message arrives as a signed `subscription.message.received` event. Look up which of your users owns the number from `whatsappNumber`, then reply through the Messages API:

    ```js theme={null}
    app.post("/wassist/webhook", express.raw({ type: "application/json" }), async (req, res) => {
      const event = Wassist.webhooks.constructEvent(
        req.body,
        req.header("x-wassist-signature"),
        process.env.WASSIST_WEBHOOK_SECRET,
      );
      res.sendStatus(200);

      if (event.event !== "subscription.message.received") return;

      const tenant = await db.tenants.findByWhatsappNumber(event.whatsappNumber);
      const reply = await tenant.handleMessage(event.contact, event.message);

      await wassist.conversations.messages.send(event.conversationId, {
        type: "text",
        text: { body: reply },
      });
    });
    ```

    Keep conversation state keyed by `conversationId`, which is unique across all of your users. See [Conversation routing](/guides/webhooks/routing) for the full event list and [Webhooks](/concepts/webhooks) for signing and retries.
  </Tab>

  <Tab title="Managed agent: Wassist replies">
    Create an agent for each user, built from their settings in your product, and connect it to their number. Wassist runs the conversation: the model, memory, WhatsApp formatting and tool calls.

    ```js theme={null}
    const agent = await wassist.agents.create({ name: `${tenant.businessName} assistant` });

    await wassist.agents.update(agent.id, {
      systemPrompt: [
        `You are the WhatsApp assistant for ${tenant.businessName}.`,
        `Opening hours: ${tenant.openingHours}.`,
        tenant.customInstructions,
      ].join("\n"),
      tools: [
        {
          name: "check_availability",
          description: "Find open booking slots for a date",
          apiSchema: {
            url: "https://api.example.com/availability",
            method: "GET",
            query_params: {
              required: ["date"],
              properties: {
                date: { type: "string", input: { type: "description", description: "The date, as YYYY-MM-DD" } },
              },
            },
            request_headers: {
              Authorization: { input: { type: "value", value: `Bearer ${process.env.PLATFORM_TOOL_SECRET}` } },
              "X-Tenant-Id": { input: { type: "value", value: tenant.id } },
            },
          },
        },
      ],
    });

    await wassist.phoneNumbers.connectAgent(number, { agentId: agent.id, applyToExisting: true });
    await db.tenants.update(tenant.id, { wassistAgentId: agent.id });
    ```

    Fixed header values like `X-Tenant-Id` tell your API which user a tool call is for. Every call also carries `X-Wassist-Conversation-Id` and `X-Wassist-Contact-Id`.

    When your user changes their settings, call `agents.update` again. Passing `tools` replaces the whole list, so send every tool each time. See [Configure tools](/guides/configure-tools) for the tool format, connectors and handoffs.
  </Tab>
</Tabs>

### Combine both

A common setup is a managed agent on every number, with your own inbox taking over individual chats when a human needs to step in. Subscribe just that conversation to your webhook, then hand it back when you're done:

```js theme={null}
// Take over one chat: your webhook now gets its messages, and the agent stops replying
await wassist.conversations.subscribe(conversationId, { webhookId: process.env.WASSIST_WEBHOOK_ID });

// Hand it back to the number's default (the agent)
await wassist.conversations.unsubscribe(conversationId);
```

## Operate your users' accounts

<AccordionGroup>
  <Accordion title="Message templates" icon="file-lines">
    You need an approved template to message a customer outside WhatsApp's 24-hour window. Meta approves templates per WhatsApp account, so create the template once in your organization and publish it to each user's account:

    ```js theme={null}
    const template = await wassist.whatsappTemplates.create({
      name: "booking_reminder",
      category: "UTILITY",
      language: "en",
      components: [
        { type: "BODY", text: "Hi {{1}}, a reminder about your booking at {{2}}.", example: { body_text: [["Sam", "10:00"]] } },
      ],
    });

    await wassist.whatsappTemplates.publish(template.id, { accountIds: [tenant.whatsappAccountId] });
    ```

    Approval status is tracked per account on the template. See [WhatsApp templates](/api-reference/whatsapp-templates/create).
  </Accordion>

  <Accordion title="Anything else Meta supports" icon="arrow-right-arrow-left">
    The [Business API proxy](/api-reference/whatsapp-account/proxy/get) forwards requests to Meta's Graph API using the access your user granted, so you can call endpoints Wassist doesn't wrap without handling Meta tokens yourself. Requires the Pro plan.
  </Accordion>

  <Accordion title="Pausing or offboarding a user" icon="user-slash">
    Stop handling a user's messages without touching their WhatsApp account:

    ```js theme={null}
    await wassist.phoneNumbers.unsubscribe(number, { applyToExisting: true });
    await wassist.agents.delete(tenant.wassistAgentId); // if you created one
    ```

    Messages are still stored while a number has no routing. Your user can revoke Wassist's access to their account at any time from Meta Business Settings.
  </Accordion>

  <Accordion title="Meta limits and verification" icon="shield-check">
    Each of your users' businesses is its own account at Meta, with its own messaging limits and display-name review. For higher limits and the verified badge, the user completes Meta business verification from Meta Business Suite. See [Business verification](/guides/connect-whatsapp#business-verification).
  </Accordion>
</AccordionGroup>

<Tip>
  Try the whole flow on yourself first: create a link session, open the `linkUrl`, and connect your own WhatsApp Business account as if you were one of your users.
</Tip>

## Related

<CardGroup cols={3}>
  <Card title="Conversation routing" icon="route" href="/guides/webhooks/routing">
    Webhook, agent and no-routing modes, per number and per conversation.
  </Card>

  <Card title="Configure tools" icon="plug" href="/guides/configure-tools">
    Give each user's agent access to their data.
  </Card>

  <Card title="Link session API" icon="link" href="/api-reference/account-link/create">
    Create, list and expire link sessions.
  </Card>
</CardGroup>
