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

# Connect your platform to your customers' applications

> For a platform that serves many businesses: a Connect button, the customer's approval on Tawked's own page, and one credential per connected application to send WhatsApp messages and manage templates with.

Your platform serves many businesses, and each of them has its own Tawked application and WhatsApp number. Instead of asking each one to create an API key and paste it into your settings, give them a **Connect** button. The customer signs in to Tawked, chooses one application, and allows your app on Tawked's own page. Your server receives a credential for that one application, and calls the Tawked API with it: `https://tawked.com/v1/...`.

This page is the whole integration: making your app, the Connect flow, a worked example of creating a template and sending it, the rules your credential meets, and how access ends.

The customer keeps the account, the number, the balance and the last word: they see what your app sent and the templates it made, and they can disconnect it at any time.

## Before you start

You make your app yourself, in the Tawked console. It takes a partner account: its owner, or an admin whose access covers every application, finds **Integration apps** in the console's sidebar. A platform that is not a partner yet [writes to Tawked](https://tawked.com/en/contact) to become one.

**1. Make the app.** On **Integration apps**, press **New app** and give:

* your app's name, in Arabic and in English, as customers will read it on Tawked's page. A name that contains "Tawked" is refused;
* your website's address;
* the exact return address or addresses of your Connect flow (`redirect_uri`), https only, five at most;
* the address of the page in your product where a customer presses Connect. It is optional;
* a square logo, PNG or JPG. It is optional: without one Tawked shows the first letter of your app's name;
* what your app does, in a few lines for Tawked's reviewer. Customers never read it, and it can wait until you send the app for review.

**2. Take the credentials** from the app's page:

* a **client id**, which is public and always shown there;
* a **client secret**, shown once, when the app is made. Keep it on your server only. If you lose it, press **Renew the secret** on the same page: the new one is shown once, and the old one stops at once.

**3. Try the flow in test mode.** Until Tawked approves your app, it connects to the applications of your own account only. Signed in to Tawked as a person of your partner account, press Connect in your product and choose one of your own applications that has a connected WhatsApp number: the whole flow below works, the exchange and the API calls included. Anyone from another account who follows your Connect link reads that the app is not available yet, and nothing is connected.

**4. Send it for review.** Press **Send for review** on the app's page. Tawked's team approves the app or sends it back with the reason; you change what the reason asks for and send it again. The answer reaches the console's alerts and your e-mail. Once the app is approved, any Tawked customer can connect it.

**After approval**, a change to the app's name, its logo, its website, a return address or the address of your Connect page waits for Tawked's review, and the current ones keep working until it is allowed. One change waits at a time. What your app does, the lines for the reviewer, changes at once.

What your app may do is fixed by Tawked, never by a request. Today every app holds the same three permissions: reading messages and templates, sending template messages, and managing templates, on Tawked Notifications only.

## The flow

<Steps>
  <Step title="Send the customer to Tawked">
    When the customer presses Connect, make a random `state` and a PKCE `code_verifier`, keep both in their session, and send the browser to:

    ```text theme={"dark"}
    https://tawked.com/oauth/authorize
      ?response_type=code
      &client_id=YOUR_CLIENT_ID
      &redirect_uri=https://yourplatform.example/tawked/return
      &state=RANDOM_STATE
      &code_challenge=BASE64URL_SHA256_OF_THE_VERIFIER
      &code_challenge_method=S256
    ```

    `redirect_uri` is one of the return addresses you gave for your app, exactly, and it is https. PKCE is required: a request without `code_challenge` is refused, `code_challenge_method` is `S256`, and `plain` is refused. Send no `scope`: the permissions are the same for every app.

    Tie `state` to the signed-in user of your own product, in their browser session. It is what keeps one customer's approval from landing in another customer's account with you.
  </Step>

  <Step title="The customer answers on Tawked's page">
    The customer signs in to Tawked if they are not signed in, and comes back to the same page. It names your app, lists what it may do, and offers the applications that can be connected: the customer's own applications that have a connected WhatsApp number. The customer picks one, then allows or refuses.

    Whoever manages the application's API keys may approve. When the person who pressed Connect may not, Tawked sends the request on to the people who may, and the page says so. Once one of them has allowed it in Tawked, the customer presses Connect in your product again: Tawked's page says the request was allowed, and one button brings them back to you with the code. Only the person who opened the request, or the one who allowed it, takes it back. Another member of the customer's account who presses Connect starts a new request. A request that nobody answers runs out after 7 days.
  </Step>

  <Step title="Read the return">
    The browser comes back to your `redirect_uri`:

    | Return | Meaning |
    | - | - |
    | `?code=...&state=...` | Allowed. Exchange the code at once: it is short-lived and works once. |
    | `?error=access_denied&state=...` | Refused. Nothing was connected. |

    Check that `state` is the one you made for this user's session before you do anything else, and refuse a return whose `state` you did not issue.
  </Step>

  <Step title="Exchange the code on your server">
    ```bash theme={"dark"}
    curl -X POST https://tawked.com/oauth/apps/token \
      -d grant_type=authorization_code \
      -d client_id=YOUR_CLIENT_ID \
      -d client_secret=YOUR_CLIENT_SECRET \
      -d code=THE_CODE \
      -d redirect_uri=https://yourplatform.example/tawked/return \
      -d code_verifier=THE_VERIFIER
    ```

    ```json theme={"dark"}
    {
      "access_token": "tk_live_xxxxxxxxxxxxxxxxxxxx",
      "token_type": "Bearer",
      "connection_id": "0f4c9c0e-6d2b-4b8a-9c3e-7a1d2e3f4a5b",
      "application": { "id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f", "name_ar": "متجر الورد", "name_en": "Flower Shop" },
      "permissions": ["notifications:read", "notifications:send", "notifications:templates"]
    }
    ```

    The answer comes once, with `Cache-Control: no-store`. `access_token` is the credential for that one application. There is no refresh token, and the credential does not expire with time. Store it like a password, encrypted, next to `connection_id` and the customer it belongs to; Tawked never shows it again.
  </Step>

  <Step title="Call the API">
    Send the credential as a Bearer key, exactly as any application key is sent:

    ```bash theme={"dark"}
    curl -X POST https://tawked.com/v1/whatsapp/messages \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: booking-8841-reminder" \
      -d '{ "to": "0551234567", "template": "session_reminder", "lang": "ar", "params": { "name": "سلمان" } }'
    ```

    The next section walks through a template and a send.
  </Step>
</Steps>

## A worked example: create a template, then send it

Every message is an approved template, and your app sends utility templates only. Create one for the customer's number, wait for Meta to approve it, then send it.

**1. Create the template.** The body's placeholders are named, and `examples` gives one sample value per placeholder, keyed `body:<key>`: Meta reviews with them.

```bash theme={"dark"}
curl -X POST https://tawked.com/v1/whatsapp/templates \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: session_reminder-ar-v1" \
  -d '{
    "name": "session_reminder",
    "language": "ar",
    "category": "UTILITY",
    "body": "مرحبًا {{name}}، نذكّرك بموعد حصتك يوم {{day}} الساعة {{time}}.",
    "examples": { "body:name": "سلمان", "body:day": "الأحد", "body:time": "5:00 م" }
  }'
```

```json theme={"dark"}
{ "id": "7100", "name": "session_reminder", "language": "ar", "category": "UTILITY", "status": "PENDING" }
```

The answer is `201`. `id` is Meta's id of this language of the template; keep it. The fields:

| Field | Rule |
| - | - |
| `name` | Required. Lowercase letters, digits and underscores. Fixed at creation. |
| `language` | Required. One of `ar`, `en`, `en_US`, `en_GB`, `ur`, `hi`, `bn`, `tl`, `id`, `fr`. Fixed at creation. |
| `category` | Required. `UTILITY`. |
| `body` | Required. Up to 1024 characters. Placeholders are all named (`{{name}}`, lowercase) or all numbered (`{{1}}`, `{{2}}`), never at the very start or end of the text. |
| `header` | Optional text header: one line, up to 60 characters, no emoji and no asterisk, one placeholder at most. |
| `footer` | Optional. One line, up to 60 characters, no placeholders. |
| `buttons` | Optional, up to 10, each with a `text` of up to 25 characters: `QUICK_REPLY`; `URL` with an https `url` that may end in `{{1}}` (two at most); `PHONE_NUMBER` with an E.164 `phone_number` (one at most). |
| `examples` | One sample value per placeholder, keyed `header:<key>`, `body:<key>` and `button:<index>`. Required whenever the template has placeholders. |

A field that breaks a rule answers `400 invalid_request`: `fields` names every refused field and `errors` gives each one's reason codes. What Meta refuses after that answers `422 template_refused`, with Meta's own sentence in `message`.

**2. Wait for Meta's review.** Read the template until its `status` is `APPROVED`. A `REJECTED` one carries Meta's reason in `rejected_reason`.

```bash theme={"dark"}
curl https://tawked.com/v1/whatsapp/templates/7100 \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**3. Send it.** `params` carries one value per placeholder, by name. `to` is a Saudi mobile number.

```bash theme={"dark"}
curl -X POST https://tawked.com/v1/whatsapp/messages \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: booking-8841-reminder" \
  -d '{
    "to": "0551234567",
    "template": "session_reminder",
    "lang": "ar",
    "params": { "name": "سلمان", "day": "الأحد", "time": "5:00 م" },
    "reference": "booking-8841"
  }'
```

```json theme={"dark"}
{
  "id": "0f4c9c0e-6d2b-4b8a-9c3e-7a1d2e3f4a5b",
  "status": "accepted",
  "to": "+966551234567",
  "template": "session_reminder",
  "lang": "ar",
  "reference": "booking-8841",
  "price_halalas": 0,
  "created_at": "2026-10-10T12:00:00+03:00"
}
```

The answer is `202`. A template Meta has not approved yet answers `422 template_not_approved`. Sending the same `Idempotency-Key` again answers the same `202` with `Idempotent-Replayed: true` and sends nothing.

**4. Read the status.** `GET https://tawked.com/v1/whatsapp/messages/{id}` with the `id` of the send answers the message with its `status`: `accepted`, then `sent`, `delivered`, `read`, or `failed` with Meta's `error`.

**Replies are the customer's.** When a recipient answers a message your app sent, the reply arrives in the customer's own Tawked Chat inbox, where the customer's team answers it. Your app does not receive replies.

**Marketing templates are not open to connected apps.** Your credential creates and sends utility templates only.

## When the exchange is refused

The exchange answers OAuth's own errors, as `{ "error", "error_description" }`:

| Answer | Why |
| - | - |
| `401 invalid_client` | The client id is unknown, the secret is wrong, or Tawked has stopped your app. |
| `400 invalid_grant` | The code is wrong, used or expired, the `code_verifier` or `redirect_uri` does not match, the application can no longer be connected, or the approval no longer stands: the person who allowed it has left the account, say, or may no longer manage that application's keys. |
| `400 unsupported_grant_type` | Anything but `authorization_code`. |
| `429` | Too many exchanges: 120 exchanges a minute for your client id from one address, and 240 a minute from one address across every client id. |

After a refused exchange, send the customer through Connect again: an approval that is still waiting gives a new code without a second question.

## What your credential may do

It reaches Tawked Notifications for that one application, within your app's permissions: it sends template messages, reads the messages it sent, lists and reads the templates, and creates, edits and deletes templates.

Creating, editing and deleting a template over the API is a connected app's alone. A business's own key, full access included, answers those three calls `403 insufficient_scope`: the business writes its templates in the Tawked console. Your credential holds `notifications:templates`, as every connected app's does.

| Call | Does | Scope |
| - | - | - |
| [`POST /v1/whatsapp/messages`](/api-reference/whatsapp/send-a-template-message) | Sends a template message | `notifications:send` |
| [`GET /v1/whatsapp/messages/{id}`](/api-reference/whatsapp/get-a-message) | Reads the status of a message this connection sent | `notifications:read` |
| [`GET /v1/whatsapp/templates`](/api-reference/whatsapp/list-templates) | Lists the number's templates | `notifications:read` |
| [`GET /v1/whatsapp/templates/{template_id}`](/api-reference/whatsapp/get-a-template) | Reads one template in full | `notifications:read` |
| [`POST /v1/whatsapp/templates`](/api-reference/whatsapp/create-a-template) | Creates a text template and submits it to Meta | `notifications:templates` |
| [`PATCH /v1/whatsapp/templates/{template_id}`](/api-reference/whatsapp/update-a-template) | Replaces its content; Meta reviews it again | `notifications:templates` |
| [`DELETE /v1/whatsapp/templates/{template_id}`](/api-reference/whatsapp/delete-a-template) | Deletes that one language of the template | `notifications:templates` |

Every path is under `https://tawked.com`.

The writes take text templates only (no image header, no authentication template) and an optional `Idempotency-Key`. `PATCH` takes the whole new content, the body of a creation without `name` and `language`. Only an `APPROVED`, `REJECTED` or `PAUSED` template is edited; anything else answers `409 template_not_editable` with a `status` that says why. `DELETE` answers `{ "id", "name", "language", "deleted": true }`.

Your credential also meets rules of its own:

| Rule | What you see |
| - | - |
| Utility templates only, in sends and in the templates you create or edit | `422 template_category_not_allowed` |
| Your own messages: a status read answers only for messages that connection sent | `404 not_found` |
| Your own templates: you read every template of the number, and edit and delete only the ones your app created for that application, marked `"managed": true` | `403 template_not_managed` |
| A template the customer's own campaign, automation or store notice still sends is not yours to edit or delete | `409 template_in_use` |
| A name that starts `salla_`, `zid_` or `wf_`, or one of Tawked's own template names, is not yours to take | `400 invalid_request`, with `nameReserved` for `name` |
| A daily cap per connection, 1,000 messages unless Tawked set another number for your app, counted from midnight, Riyadh time | `429 too_many_requests` |
| A template quota per connection, 20 templates that connection created and Meta still lists | `422 template_refused`, with the reason in `message` |
| One rate limit for your app across all its connections, 600 calls a minute, beside the limit of each credential | `429 too_many_requests` |

Messages are charged to the customer's balance and sent from the customer's number, like any message of that application. The customer sees every template your app made and every message it sent.

## When access ends

Access ends when the customer disconnects your app in Tawked, or revokes its key, and while Tawked has stopped your app. From then on every call answers `401 unauthorized`. Treat that answer as "not connected": stop sending for that customer and show Connect again.

Connecting the same application again issues a new credential and retires the old one, on the same `connection_id`. Replace what you stored.

## More than one application

A credential is for one application. A customer with a second application presses Connect again and chooses it: you receive a second credential and a second `connection_id`, and the first keeps working.

## Checklist

* Keep the client secret and every credential on your server, never in a browser or a mobile app.
* Bind `state` to your user's session and check it on every return.
* Store one credential per `connection_id`, encrypted.
* Send an `Idempotency-Key` on every send and every template write.
* Match errors on the `error` string. The [Errors](/errors) page lists every code.
* On `401 unauthorized`, mark the customer as disconnected.


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