Reference
Widget API
The browser-facing API the widget uses. Most shops never call it directly. Use it to build a fully custom chat interface.
Browser only
site_key in the query string and only answers browsers on your allowed domains (CORS is decided per shop). Chat endpoints also need a session token obtained with a proof-of-work challenge. Server-side calls are rejected by design (see the security model).Get the widget configuration
Returns the shop’s display info and its published widget configuration (with plan rules applied). Cacheable for 60 s; supports ETag / If-None-Match.
{
"shop": { "name": "Lumière Beauty", "defaultLocale": "en", "enabledLocales": ["en", "ro"], "currency": "EUR", "identifyEnabled": false },
"config": { "theme": { "primaryColor": "#9d3b5a", "mode": "light", "…": "…" }, "launcher": { "…": "…" }, "behavior": { "…": "…" } },
"active": true
}Create a session (proof-of-work)
{ "challenge": "kq3…", "salt": "9f2c…e1", "difficulty": 17, "expiresAt": "2026-10-01T12:02:00.000Z" }Find a decimal nonce such that sha256(salt + ":" + nonce) starts with difficulty zero bits. Challenges are single-use and expire after 2 minutes. Then exchange it for a session:
| Field | Type | Description |
|---|---|---|
visitor_anon_idrequired | string | Random id you generate and keep per browser ([A-Za-z0-9_-]{8,64}). Create and store it only once the shopper uses the chat, as the AskMerra widget does (see the cookie policy). |
challengerequired | string | The challenge id. |
noncerequired | string | Your solution. |
{ "token": "v1.eyJzIjoi….Qm9…", "expiresAt": "2026-10-01T12:30:00.000Z" }The token is valid for 30 minutes for this shop, this visitor id and this website only. On 401 session_expired, get a new one and retry.
async function solve(salt, difficulty) {
const enc = new TextEncoder();
for (let nonce = 0; ; nonce++) {
const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', enc.encode(salt + ':' + nonce)));
let bits = 0;
for (const byte of hash) {
if (byte === 0) { bits += 8; continue; }
bits += Math.clz32(byte) - 24;
break;
}
if (bits >= difficulty) return String(nonce);
}
}The official widget uses a synchronous SHA-256 in time slices (≈0.2 s at difficulty 17 on a laptop) so the page stays responsive.
Send a message (streaming)
| Field | Type | Description |
|---|---|---|
session_tokenrequired | string | From /widget/session. |
visitor_anon_idrequired | string | Same id the session was created for. |
messagerequired | string ≤1000 | The visitor’s message. |
conversation_id | uuid | Continue a conversation (from a previous meta event). Omit to start a new one. |
locale | string | Interface language; the reply follows the language of the message. |
page_url | string | Current page URL. |
page_context.product_id | string | external_id or SKU of the product on the current page. |
The response is a text/event-stream:
| Event | Data |
|---|---|
meta | { conversationId, userMessageId, locale }, always first |
delta | { text }. Append to the answer (Markdown-lite: bold, italic, lists, links) |
products | { products: [{ externalId, sku, name, brand, url, imageUrl, price, salePrice, currency, inStock }] } |
escalation | { reason }. Offer the contact form |
unavailable | { reason }, one of paused | no_plan | quota | hard_cap | no_credits | billing | busy. Show a polite message + contact form |
done | { messageId, route, locale }. Answer complete; messageId is used for feedback |
error | { code, message } |
Conversation history
Messages of a conversation from the last 24 hours, for restoring the chat after a page load. Returns 404 for older or foreign conversations.
Feedback
| Field | Type | Description |
|---|---|---|
session_token / visitor_anon_idrequired | string | As above. |
message_idrequired | uuid | From the done event. |
valuerequired | 1 | -1 | Thumbs up / down. A thumbs down removes the answer from the cache. |
Contact form
| Field | Type | Description |
|---|---|---|
conversation_idrequired | uuid | The current conversation. |
emailrequired | Visitor’s email, used as reply-to. | |
messagerequired | string ≤2000 | Visitor’s message to the team. |
name | string | Visitor’s name. |
Analytics events
Batches of up to 20 events (widget_open, product_click, add_to_cart_click, suggestion_click), each with an optional conversation_id and a small payload. Send the JSON body as text/plain so it works with navigator.sendBeacon on page unload. Responds 204.
Purchases (sales tracking)
Links a purchase to the visitor’s chat. Body: session_token, visitor_anon_id and order with id (unique per order), value, currency (ISO 4217), items (up to 200 of { id, name?, price?, quantity }) and source (datalayer or api). Sent as text/plain JSON. Responds { "recorded": true } when the visitor chatted in the 7 days before; otherwise nothing is stored and it responds { "recorded": false }. The same order id is recorded once. The widget calls this only with the shopper’s analytics consent. 20 orders per visitor per hour.
Identify a visitor
Attaches email and/or name to the visitor. Only allowed when visitor identification is enabled in the shop settings (otherwise 403 identify_disabled).