Web Widget
Embed Taliiq voice and chat on your website with domain allowlisting and optional JWT identity verification.
Taliiq’s Web Widget delivers real-time voice and chat from the browser. Embed a script tag, restrict origins to your domains, and optionally inject verified user identity via a server-signed JWT.
Setup
Go to Build → Channels and create a Web channel.
Assign a deployed agent with voice and/or chat capabilities.
Add allowed domains as hostnames (e.g. mycompany.com). Do not include https://. For local testing, list both localhost and 127.0.0.1 if you use either host. Allow-all (*) is not supported.
Copy the anonymous embed snippet from the channel page.
Anonymous embed
<script
src="https://widget.taliiq.com/v1/widget.js"
data-channel-id="YOUR_CHANNEL_UUID"
data-link-source="homepage-hero"
></script>Visitors stay anonymous. Styling (brand color, position, icon) is configured on the channel in the dashboard.
Optional data-link-source (or data-source) tags the placement. The widget also sends the page URL, referrer, and UTM params so chat/voice sessions can identify where the visit started.
The widget footer “Powered by Taliiq” link opens taliiq.com with source=web_widget_chat or source=web_widget_voice plus origin=<embed-hostname> so landing can store attribution cookies and forward them when the visitor continues to the dashboard.
Appearance overrides
Position, theme mode, brand color, and launcher icon come from the channel settings in the dashboard. You can override them per page from the embed snippet, for example to place the widget on the left for Arabic pages or match a page's color scheme.
<script
src="https://widget.taliiq.com/v1/widget.js"
data-channel-id="YOUR_CHANNEL_UUID"
data-position="auto"
data-theme-mode="light"
data-theme-color="#7C3AED"
data-icon="headset"
></script>| Attribute | Allowed values | Notes |
|---|---|---|
data-position | auto, bottom-right, bottom-left, middle-right, middle-left, top-right, top-left | auto uses bottom-left when the page is right-to-left (dir="rtl" on <html> or <body>) and bottom-right otherwise, and moves live when dir changes |
data-theme-mode | light, dark | |
data-theme-color | Hex color: #RGB or #RRGGBB | Brand color for the launcher, buttons, and accents |
data-icon | message-circle, bot, sparkles, headset, phone, mic, headphones, zap | Launcher and chat header icon |
Precedence: a valid override always wins over the dashboard value. Missing or invalid values are ignored and the dashboard value is used. Add data-debug="true" to see a console warning for rejected values.
Change appearance live
For single-page apps (language switch, theme toggle), call the JavaScript API. Changes apply immediately, even with the chat open or during a call, and never reset the conversation. Calls made before the widget finishes loading are applied as soon as it is ready.
// Arabic selected: move the widget to the left
window.TaliiqWidget.update({ position: 'bottom-left' });
// Dark mode toggle with a custom brand color
window.TaliiqWidget.update({ themeMode: 'dark', themeColor: '#0EA5E9', icon: 'sparkles' });
// Read what is currently applied
window.TaliiqWidget.getAppearance();
// → { position: 'bottom-left', themeMode: 'dark', themeColor: '#0EA5E9', icon: 'sparkles' }Unknown keys and invalid values are ignored; update() never throws. Use position: 'auto' to go back to direction-based placement.
Links in agent replies
Web addresses (https://…, www.…), email addresses, and markdown links ([label](https://…)) in agent chat replies render as clickable links. Web links open in a new tab; emails open the visitor's mail app. Only http, https, and mailto links are ever created, anything else (for example javascript:) stays plain text. Visitor messages are never turned into links.
Host page events
The widget dispatches a taliiq:session-end event on window when a conversation ends, so your page can react (show an offer, open a survey, fire analytics):
detail.kind | Fires when | Other fields |
|---|---|---|
voice | A connected voice call ends (visitor hangs up or the call disconnects). Calls that never connected do not fire. | durationSec, channelId |
chat | The chat panel is closed, or the chat is ended from the menu, after new agent replies since the last event. | assistantReplies, channelId |
<script>
window.addEventListener('taliiq:session-end', (event) => {
const { kind, channelId } = event.detail;
if (kind === 'voice' && event.detail.durationSec >= 20) {
// e.g. show a follow-up offer
}
});
</script>The event carries no transcript or personal data. Listener errors never affect the call or chat.
Identity verification (JWT)
To attach a known customer to the session, your server mints a short-lived JWT signed with the channel Identity Secret. Taliiq verifies the JWT with that same secret and trusts only the claims inside it.
JWT shape (HS256)
| Claim | Required | Description |
|---|---|---|
user_id | Yes | Stable customer id in your system |
exp | Yes | Expiry (Unix timestamp) |
iat | Recommended | Issued-at |
user_email | No | |
user_name | No | Display name |
metadata | No | JSON object of extra attributes |
Rules:
- Anonymous (no
data-user-token/identity_token) works without a JWT. - When a token is present, it must verify with the channel identity secret.
- Never put the Identity Secret in HTML, the widget script, or client-side JavaScript.
- Reveal or rotate the secret under Identity verification on the web channel edit page — you can copy it anytime for plugins.
Identified embed
<script
src="https://widget.taliiq.com/v1/widget.js"
data-channel-id="YOUR_CHANNEL_UUID"
data-user-token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
></script>Server / plugin pattern
Store channel id + identity secret in your admin settings. On page render, mint a JWT and print data-user-token.
<?php
// WordPress / WooCommerce / Magento-shaped example (pseudocode)
$now = time();
$payload = [
'user_id' => (string) $current_user->ID,
'user_email' => $current_user->user_email,
'user_name' => $current_user->display_name,
'iat' => $now,
'exp' => $now + 3600,
];
// Use any JWT library (firebase/php-jwt, lcobucci/jwt, …)
$identity_jwt = JWT::encode($payload, $taliiq_identity_secret, 'HS256');
?>
<script
src="https://widget.taliiq.com/v1/widget.js"
data-channel-id="<?php echo esc_attr($taliiq_channel_id); ?>"
data-user-token="<?php echo esc_attr($identity_jwt); ?>"
></script>Official WooCommerce, Magento, and WordPress plugins will use this same contract.
Security checklist
- List explicit hostnames only (
example.com,localhost,127.0.0.1). Allow-all (*) is not supported. - Keep the Identity Secret on the server / in plugin settings only.
- Use short JWT TTLs (e.g. 1 hour) and rotate the secret if it leaks.
- If your organization uses zero data retention, verified user context is stripped from sessions.
- If your site sends a
Permissions-Policyheader, make sure it allowsmicrophonefor your own origin (e.g.microphone=(self)). Voice calls and chat dictation both needgetUserMedia, and a policy that blocks it (microphone=()) fails silently, no permission prompt, no error, the browser just never asks and the feature quietly does nothing. - If your site sends a
Content-Security-Policyheader, allowlist these origins or the widget fails silently (chat may still work while voice specifically breaks, since the realtime voice library loads on demand only when a call starts):script-src:https://widget.taliiq.comhttps://cdn.jsdelivr.netconnect-src:https://widget.taliiq.comhttps://api.taliiq.comhttps://runtime.taliiq.comwss://runtime.taliiq.comhttps://room.taliiq.comwss://room.taliiq.commedia-src:blob:(voice call audio playback)
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Browser CORS error calling the API | Origin hostname not in the channel’s allowed domains, or localhost vs 127.0.0.1 mismatch |
403 Origin header is required | Request missing browser Origin (common for non-browser clients) |
403 Origin not authorized | Hostname not allowlisted, or channel still has legacy * (edit and set real domains) |
403 Invalid identity token | Wrong secret, wrong algorithm (must be HS256), or malformed JWT |
403 Identity token has expired | Increase exp when minting, or mint closer to page load |
| Voice call rings then drops back to chat, or dictate does nothing, with no browser permission prompt ever shown | Host page's Permissions-Policy blocks microphone. Check for a header like microphone=() and change it to microphone=(self) |
| Chat works but voice calls fail silently, browser console shows a blocked script/connect request | Host page's Content-Security-Policy is missing one of the origins in the Security checklist above |
Offline failsafe
If the voice agent does not connect in time, configure Offline Failsafe for that web channel.
See Channels overview for SIP and outbound options.

