Embed chat widget
Show your Wabot inbox inside your own CRM, helpdesk or admin panel. Users who are signed in to your app open the chat automatically, with no second login, and only see your account.
How it works: you create an embed secret and keep it on your server. Your server uses it to sign a short-lived token. Your page loads the chat in an iframe and gives it the token.
1. Enable embedding
Section titled “1. Enable embedding”This step needs an admin.
- In Wabot, open Chat, click the ⋮ menu and choose Embed chat in your app.
- Under Allowed sites, add each origin that will show the chat, with no path, for example
https://crm.yourcompany.com. Addhttp://localhost:3000while you test. You can add up to 20. - Click Generate secret. The secret starts with
wbe_and is shown only once. Store it on your server asWABOT_EMBED_SECRET.
2. Sign a token on your server
Section titled “2. Sign a token on your server”Create an HS256 JWT, signed with your embed secret:
| Claim | Required | Description |
|---|---|---|
account_id |
Yes | Your Wabot account ID |
exp |
Yes | Expiry in Unix seconds. At most 24 hours ahead; we recommend 1 hour |
user.email, user.name |
No | If the email matches a Wabot user in your account, replies are sent as that user. Otherwise they’re sent as the account owner |
channel_id |
No | Open a specific channel. If you leave it out, the default channel is used |
// npm i jsonwebtokenconst jwt = require('jsonwebtoken');
app.get('/wabot-embed-token', requireLogin, (req, res) => { const token = jwt.sign( { account_id: YOUR_ACCOUNT_ID, user: { email: req.user.email, name: req.user.name }, // optional // channel_id: 123, // optional }, process.env.WABOT_EMBED_SECRET, { algorithm: 'HS256', expiresIn: '1h' } ); res.json({ token });});// composer require firebase/php-jwtuse Firebase\JWT\JWT;
$token = JWT::encode([ 'account_id' => YOUR_ACCOUNT_ID, 'user' => ['email' => $user->email, 'name' => $user->name], 'exp' => time() + 3600,], getenv('WABOT_EMBED_SECRET'), 'HS256');
echo json_encode(['token' => $token]);# pip install pyjwtimport jwt, os, time
token = jwt.encode( { "account_id": YOUR_ACCOUNT_ID, "user": {"email": user.email, "name": user.name}, "exp": int(time.time()) + 3600, }, os.environ["WABOT_EMBED_SECRET"], algorithm="HS256",)var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("WABOT_EMBED_SECRET")));var token = new JwtSecurityToken( claims: new[] { new Claim("account_id", YOUR_ACCOUNT_ID.ToString(), ClaimValueTypes.Integer), }, expires: DateTime.UtcNow.AddHours(1), signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));var jwtString = new JwtSecurityTokenHandler().WriteToken(token);3. Add the chat to your page
Section titled “3. Add the chat to your page”<iframe id="wabot-chat" src="https://app.wabot.shop/embed-chat" style="width:100%;min-height:640px;border:0" allow="clipboard-write; autoplay; encrypted-media"></iframe>
<script> // Hand the chat a fresh token (keeps it out of the URL), and again when asked. const frame = document.getElementById("wabot-chat"); async function sendToken() { const { token } = await fetch("/wabot-embed-token").then(r => r.json()); frame.contentWindow.postMessage({ type: "wabot:embed-token", token }, "*"); } window.addEventListener("message", (e) => { if (e.source !== frame.contentWindow) return; if (["wabot:embed-waiting", "wabot:embed-token-expiring"].includes(e.data?.type)) sendToken(); if (e.data?.type === "wabot:embed-error") console.warn("Wabot chat:", e.data.message); });</script>function WabotChat() { const ref = React.useRef(null); React.useEffect(() => { const send = async () => { const { token } = await fetch('/wabot-embed-token').then((r) => r.json()); ref.current?.contentWindow?.postMessage({ type: 'wabot:embed-token', token }, '*'); }; const onMessage = (e) => { if (e.source !== ref.current?.contentWindow) return; if (['wabot:embed-waiting', 'wabot:embed-token-expiring'].includes(e.data?.type)) send(); }; window.addEventListener('message', onMessage); return () => window.removeEventListener('message', onMessage); }, []); return <iframe ref={ref} src="https://app.wabot.shop/embed-chat" style={{ width: '100%', minHeight: 640, border: 0 }} />;}<!-- Simplest option: the token goes in the URL fragment, which is never sent to servers --><iframe src="https://app.wabot.shop/embed-chat#token=SIGNED_TOKEN_FROM_YOUR_SERVER" style="width:100%;min-height:640px;border:0"></iframe>The chat removes the token from the address bar after reading it.
Give the iframe a height of at least 600 px. The width adapts automatically.
Messages between your page and the chat
Section titled “Messages between your page and the chat”| Message | Direction | Meaning |
|---|---|---|
wabot:embed-token |
Your page → chat | {type, token}. Sends a token, or a fresh one to extend the session |
wabot:embed-waiting |
Chat → your page | The chat loaded without a token and waits up to 4 seconds for one |
wabot:embed-ready |
Chat → your page | The session is ready. Includes {account_id} |
wabot:embed-token-expiring |
Chat → your page | About 2 minutes before the session ends. Send a new token |
wabot:embed-error |
Chat → your page | Something went wrong. Includes {message} |
Always check event.source === frame.contentWindow before you act on a message.
What embedded users can do
Section titled “What embedded users can do”- Read and reply to conversations, and send templates
- Manage contacts, tags and notes
Settings, billing, campaigns and other admin areas aren’t available in the embed. Each session is locked to one account and lasts until the token’s exp, up to 12 hours.
Security checklist
Section titled “Security checklist”- Only sign tokens for users who are signed in to your app.
- Keep expiries short (1 hour), and sign a new token on each page load.
- Serve your app over HTTPS, and only list your real sites as allowed.
- Rotate the secret if it leaks, or when people who had access leave.
Test your setup
Section titled “Test your setup”curl -X POST https://app.wabot.shop/embed/session \ -H "Content-Type: application/json" \ -d '{"token":"<signed token>","parentOrigin":"https://crm.yourcompany.com"}'A working token returns 200 with account_id, channel_id and expiresAt.
Troubleshooting
Section titled “Troubleshooting”| Message | Fix |
|---|---|
| Chat embedding is not enabled for this account | Generate the embed secret (step 1) |
| No sites are allowed to embed this chat yet | Add your site under Allowed sites |
| This site is not allowed to embed the chat | Add the exact origin: scheme, host and port |
| Invalid embed token | Wrong secret, the algorithm isn’t HS256, or the token was changed |
| Embed token expired | Sign a new token |
| Embed token must have an exp | Set an expiry |
| Embed token lifetime is longer than 24 hours | Use a shorter expiry |
| Embed token has no valid account_id | Include your numeric account_id |
| Too many requests, slow down | Token exchanges are limited to 30 per minute per IP. Don’t sign a token on every render |
