> ## 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.

# Conversation menus

> Show a page from your own systems beside every conversation in Tawked Chat, with the open customer filled into its link or handed to it by a script.

A conversation menu («قوائم المحادثة» in the Arabic console) is a page of yours that Tawked Chat shows beside every conversation of the account: a tab beside the conversation on the web, and an item in the conversation's menu in the phone app, which opens the page full screen. Use it in one of two ways:

* **No code.** Put tokens such as `{customer_phone}` in the link. Tawked fills them with the open conversation's values before your page opens.
* **Code.** Add one script to your page. It hands your page the open conversation: its id, the customer and the agent.

<Prompt description="Ask your coding assistant to build a conversation menu page." actions={["copy", "cursor"]}>
  Build a page that Tawked Chat shows beside a conversation (docs: [https://docs.tawked.com/chat-menus](https://docs.tawked.com/chat-menus)).

  * Serve it over https on port 443 from a public domain name. Send the header Content-Security-Policy: frame-ancestors 'self' [https://chat.tawked.com](https://chat.tawked.com) and no X-Frame-Options DENY or SAMEORIGIN.
  * Load [https://tawked.com/js/tawked-chat-menu.js](https://tawked.com/js/tawked-chat-menu.js) and call TawkedChat.onConversation with a callback that takes one argument, c. c.conversation.id is always set; c.customer.name, c.customer.phone, c.customer.email and c.agent.name are strings or null.
  * The callback may run more than once: replace what the page shows each time, never append. If it has not run after about 8 seconds, show a message that the page opens from Tawked Chat.
  * Treat c as a hint, never as authentication. Load customer data only for a user signed in to our own app, and check on the server that this user may see it.
  * Put no secret in the page's link or its code.
</Prompt>

## Add a menu

The menus belong to the account: one list for every application and every conversation.

<Steps>
  <Step title="Open the page">
    In the console, open an application with Tawked Chat, then **Chat → Conversation menus**. If the account has no Tawked Chat yet, the page asks you to turn it on first.
  </Step>

  <Step title="Add the menu">
    Press **Add menu**, type the **Name** your team sees and the **Link** of your page. Press a token chip under the link (**Customer phone**, **Customer name**, **Customer email**, **Conversation number**) to add it to the link. The preview shows the page as your team will see it, with a sample customer.
  </Step>

  <Step title="Save and reload Tawked Chat">
    Press **Add menu**. Tawked checks whether your site opens inside Tawked Chat and shows the answer in the list's **Status** column. Your team sees the new menu after reloading Tawked Chat.
  </Step>
</Steps>

| Rule | Value |
| - | - |
| Name | 2 to 30 characters, unique in the account. The phone app tells menus apart by name. |
| Link | Starts with `https://`, at most 1024 characters. The host is a public domain name that resolves to a public address: no IP address, no port other than 443, no user name or password in the link, no `localhost` or `.local`/`.internal` name. A token cannot sit in the host. |
| Menus per account | At most 10. The Salla orders tab that Tawked adds for a Salla store is not counted. |
| Order | The order the menus were added in. |
| Who adds, edits and deletes | The account's owner, and admins whose access covers the whole account. Everyone else who opens Chat sees the list read-only. |
| When agents see a change | After they reload Tawked Chat. |

Every change is recorded with who made it. To change a menu, open it from the list, then **Save**, or **Delete menu**.

## Link tokens

A token is a name in braces inside the link. Tawked replaces each one with the open conversation's value.

| Token | Chip in the console | Replaced with | Example value |
| - | - | - | - |
| `{customer_phone}` | Customer phone | The customer's phone number as Tawked Chat holds it, in international format | `+966551234567` |
| `{customer_name}` | Customer name | The customer's name as Tawked Chat holds it | `Sara Alotaibi` |
| `{customer_email}` | Customer email | The customer's email, when Tawked Chat has one | `sara@example.com` |
| `{conversation_id}` | Conversation number | The conversation's number in Tawked Chat | `1042` |

* **Encoded.** Each value is URL-encoded before it goes into the link: `+966551234567` becomes `%2B966551234567`, and an Arabic name becomes percent-encoded UTF-8. Your server decodes it like any query value.
* **Empty when missing.** A value the conversation does not have becomes an empty string. Your page must handle `?email=` with nothing after it.
* **Path or query.** A token can sit in the path or the query: `https://crm.example.com/customers/{customer_phone}` and `https://crm.example.com/search?phone={customer_phone}` both work.
* **The agent's name is not a token.** Only the [script](#read-the-open-conversation-in-your-page) gives it.

```text theme={"dark"}
https://crm.example.com/lookup?phone={customer_phone}&name={customer_name}&conversation={conversation_id}
```

opens, for the sample customer, as

```text theme={"dark"}
https://crm.example.com/lookup?phone=%2B966551234567&name=Sara%20Alotaibi&conversation=1042
```

**How the values reach the link.** A link with a token opens through a short page of Tawked's first. That page asks Tawked Chat for the open conversation, fills the tokens, and replaces itself with your page in the same tab. If no answer comes after five asks, 1.5 seconds apart, it opens your page with every token empty. The values are those of the conversation open when the menu opened.

## Read the open conversation in your page

Load the script, then register a callback. This is a complete page:

```html theme={"dark"}
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Customer orders</title>
</head>
<body>
  <p id="state">Waiting for the conversation…</p>
  <dl>
    <dt>Conversation</dt><dd id="conversation"></dd>
    <dt>Customer</dt><dd id="name"></dd>
    <dt>Phone</dt><dd id="phone"></dd>
    <dt>Email</dt><dd id="email"></dd>
    <dt>Agent</dt><dd id="agent"></dd>
  </dl>

  <script src="https://tawked.com/js/tawked-chat-menu.js"></script>
  <script>
    var received = false;

    TawkedChat.onConversation(function (c) {
      received = true;
      document.getElementById('state').textContent = '';
      // Replace, never append: the callback can run again for another conversation.
      document.getElementById('conversation').textContent = String(c.conversation.id);
      document.getElementById('name').textContent = c.customer.name || '';
      document.getElementById('phone').textContent = c.customer.phone || '';
      document.getElementById('email').textContent = c.customer.email || '';
      document.getElementById('agent').textContent = c.agent.name || '';
      // Load your own data here, for your own signed-in user, for example:
      // fetch('/api/orders?phone=' + encodeURIComponent(c.customer.phone || ''), { credentials: 'include' })
    });

    // Opened outside Tawked Chat: the callback never runs.
    setTimeout(function () {
      if (!received) {
        document.getElementById('state').textContent = 'Open this page from a conversation in Tawked Chat.';
      }
    }, 8000);
  </script>
</body>
</html>
```

`TawkedChat.onConversation(callback)` calls `callback(c)` with one object:

| Field | Type | Value |
| - | - | - |
| `c.conversation.id` | number | The conversation's number in Tawked Chat, as Tawked Chat sends it. Never null: the callback does not run without it. Compare it as `String(c.conversation.id)` if you store it as text. |
| `c.customer.name` | string or null | The customer's name. |
| `c.customer.phone` | string or null | The customer's phone number, in international format such as `+966551234567`. |
| `c.customer.email` | string or null | The customer's email. |
| `c.agent.name` | string or null | The name of the team member who has the conversation open. |

There are no other fields. Read nothing else from `c`.

**When the callback runs**

* The script asks Tawked Chat for the open conversation as soon as you register a callback, then again every 1.5 seconds, five asks in all, and stops at the first answer.
* The callback runs when the answer arrives. A callback registered after that runs at once with the last conversation.
* If Tawked Chat sends a different conversation to the same page later, the callback runs again with it. It never runs twice in a row for the same conversation. Write it so that running again replaces what the page shows.
* Opened outside Tawked Chat, no answer comes and the callback never runs. Show a message instead, as the example does.
* In the console's preview, when your site lets `https://tawked.com` frame it, the callback runs once with the sample customer: Sara (`سارة العتيبي`), `+966551234567`, `sara@example.com`, conversation `1042`.
* An error thrown inside your callback is caught and logged to the browser console; other callbacks still run. You can register more than one.
* Loading the script twice is harmless: the second copy does nothing.

**Which messages the script trusts.** It reads a message only when it comes from `https://chat.tawked.com`, from `https://tawked.com` (the console's preview, with the sample customer), from your page's own origin, or with an empty origin (how the phone app delivers it), and only when it has the shape of a conversation. Everything else is ignored. Its ask carries no data, so it goes to whichever page frames yours.

<Note>
  The script works the same on the web and in the phone app. It works with or without link tokens: a menu whose link has tokens opens your page in the same tab, and the script on that page then talks to Tawked Chat directly.
</Note>

## Opening inside Tawked Chat

On the web, Tawked Chat shows your page in a frame inside `https://chat.tawked.com`. Your site must allow that:

* Send `Content-Security-Policy: frame-ancestors` with `https://chat.tawked.com` in the list, **or**
* send no `frame-ancestors` at all and no `X-Frame-Options: DENY` or `X-Frame-Options: SAMEORIGIN`.

When both headers are present, `frame-ancestors` decides, as in browsers. `X-Frame-Options: ALLOW-FROM` is ignored. The header to add:

```http theme={"dark"}
Content-Security-Policy: frame-ancestors 'self' https://chat.tawked.com
```

Add `https://tawked.com` to the list as well if you want the console's preview to show your live page, with the sample customer handed to the script; without it the preview shows a drawing of where the page goes.

<CodeGroup>
  ```nginx nginx theme={"dark"}
  add_header Content-Security-Policy "frame-ancestors 'self' https://chat.tawked.com" always;
  ```

  ```javascript Express theme={"dark"}
  app.use('/tawked-menu', (req, res, next) => {
    res.removeHeader('X-Frame-Options');
    res.setHeader('Content-Security-Policy', "frame-ancestors 'self' https://chat.tawked.com");
    next();
  });
  ```

  ```javascript Next.js theme={"dark"}
  // next.config.js
  module.exports = {
    async headers() {
      return [{
        source: '/tawked-menu/:path*',
        headers: [{ key: 'Content-Security-Policy', value: "frame-ancestors 'self' https://chat.tawked.com" }],
      }];
    },
  };
  ```

  ```php Laravel theme={"dark"}
  // In a middleware on the menu's route
  $response = $next($request);
  $response->headers->remove('X-Frame-Options');
  $response->headers->set('Content-Security-Policy', "frame-ancestors 'self' https://chat.tawked.com");
  return $response;
  ```
</CodeGroup>

If your site already sends a `Content-Security-Policy`, add `https://chat.tawked.com` to its existing `frame-ancestors` list rather than sending a second header: every policy a page sends must allow the frame.

**What the console's check answers.** When you add or save a menu, Tawked reads your link's headers (with the tokens filled with the sample) and records one of three answers:

| Status in the list | Meaning | What your team sees on the web |
| - | - | - |
| **Works** | Your site allows `https://chat.tawked.com` to frame it. | Your page, inside the conversation. |
| **Opens in a window** | Your site refuses the frame. | A card, "This site does not open inside the conversation", with **Open in a new window**, which opens your page with the tokens filled. |
| No status | Tawked could not tell: the site did not answer, answered `401` or a `5xx`, or redirected to a sign-in page. | Your page in the frame. If your site does refuse frames, the frame stays blank. |

The check is advice: a menu is saved whatever it answers. The check runs again only when you save the menu, so after changing your site's headers, open the menu and press **Save**. In the phone app your page always opens full screen, so the frame rule does not apply there.

## Security

* **The conversation is a hint, not proof.** Anyone who knows your page's address can open it, with any values in the link, and can send your page a message that looks like a conversation. Never treat `c` or the link's values as a signed-in user or as permission to see data.
* **Sign your own user in.** Load customer data only for a user signed in to your own app, and check on your server that this user may see this customer. The values only say which customer to show.
* **No secrets in the link.** Every agent of the account can read the menu's link in Tawked Chat. Do not put API keys, passwords or tokens of your own in it. Your Tawked API key never belongs in a page.
* **Signing in inside a frame.** On the web your page is a frame on another site, so your session cookie needs `SameSite=None; Secure` to be sent, and browsers that block third-party cookies may not send it at all. Many sign-in pages also refuse to be framed. If your sign-in cannot work in the frame, offer a link that opens your page in a new window.

## For AI coding agents

<Info>
  The facts to get right, in one place:

  * Script: `<script src="https://tawked.com/js/tawked-chat-menu.js"></script>`, then `TawkedChat.onConversation(function (c) { ... })`. There is no other function.
  * `c` is exactly `{ conversation: { id }, customer: { name, phone, email }, agent: { name } }`. `conversation.id` is always set (a number); the other four are strings or null.
  * The callback can run more than once (another conversation) and never runs outside Tawked Chat, except once with a sample customer in the console's preview. Replace the page's content each time; show a message if nothing arrives after about 8 seconds.
  * Link tokens: `{customer_phone}`, `{customer_name}`, `{customer_email}`, `{conversation_id}`. Values are URL-encoded and empty when missing. There is no token for the agent.
  * Link: `https://` only, a public domain name (no IP address, no port but 443), at most 1024 characters. To try a page on your machine, use a public HTTPS tunnel. Name: 2 to 30 characters, unique. At most 10 menus per account. The owner and account-wide admins manage them in **Chat → Conversation menus**.
  * Framing: send `Content-Security-Policy: frame-ancestors 'self' https://chat.tawked.com` and no `X-Frame-Options` DENY or SAMEORIGIN. Otherwise the web opens the page in a new window.
  * Security: the data is a hint, never authentication. Sign the user in with your own app and authorize on your server. No secrets in the link or the page.
  * Tawked Chat has no API: a menu is added in the console, not by a request.
</Info>


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