Esta página se publica en inglés y en ucraniano; abajo está la versión en inglés.
Beacons, handoff and conversation handoff
For developers, in code: POST /followed and /handoff/opened — the two beacons that count outcomes; POST /handoff — the lead; GET /conversation — continuing a chat across a site family.
Four small endpoints finish what a turn started. Two are beacons: they always answer 204,
whatever happened, because they are sent as the page unloads and reveal nothing. All four carry
the widget key in the body or a header, and the Origin is still checked.
POST /followed — the visitor went where the assistant pointed
Sent with navigator.sendBeacon as the widget navigates (a WebSocket frame would race the
unload), and on a citation click.
{ "widget_key": "wk_…", "conversation_id": "…", "url": "https://…/pricing", "kind": "suggestion" }
kind names the offer the follow answers — auto_redirect, suggestion, confirmed,
citation — and must match it: a follow filed under another kind lands in a different cell and
reads as zero follow-through. A follow may only increment an existing offer and never exceed
it, so the per-page percentage is honest and nobody can mint rows. Always 204: a bad body, an
unknown conversation, a rate limit (120 a minute) are all silent.
POST /handoff — the visitor left a contact
A fetch, not a beacon — it has an outcome the visitor waits for.
{
"widget_key": "wk_…",
"conversation_id": "…",
"code": "default",
"contact": "olena@example.com",
"comment": "Call after 18:00",
"source": "asked"
}
| Field | Meaning |
|---|---|
contact |
an email or a phone number; the kind is detected, never chosen |
comment |
optional, up to 5,000 characters |
code |
the widget — so a lead from the «Contact us» menu with no conversation still resolves an agent |
source |
agent (the assistant gave up), asked (the visitor asked), menu (the button) |
| Status | Meaning |
|---|---|
200 { "ok": true } |
saved: the contact encrypted, the question snapshotted; the owner is emailed after the commit, and a delivery tool, if set, is called in the background |
422 "unrecognised_contact" |
not an email and not a phone — the widget asks again |
404 |
the conversation is not this site’s |
429 |
over 30 a minute |
POST /handoff/opened — the visitor pressed a link-out
A beacon for a pressed WhatsApp / Telegram / Viber / email button: without it an owner whose only escalation is a link would see zero handoffs forever.
{
"widget_key": "wk_…",
"conversation_id": "…",
"code": "default",
"source": "agent",
"channel": "whatsapp"
}
The channel is matched against the agent’s own targets, not believed; an unknown channel is
filed under a configured one (the alphabetically first). Always 204.
GET /conversation?token=… — continuing across a site family
localStorage does not cross origins. When the assistant navigates to a page on another origin
listed on the same widget key, the runtime appends ?sa-conversation=<token>; the widget on the
destination exchanges it once:
GET /conversation?token=…
X-Widget-Key: wk_…
200 { "conversation_id": "…", "messages": [{ "role": "user" | "assistant", "text": "…" }] } — the
recent transcript, up to 50 messages. 400 invalid or expired (90 s), 403 another key’s token,
409 already used, 404 unknown conversation. The widget then strips the token from the URL so
it cannot leak.
A conversation id is a bearer capability; the single-use, short-lived token is what keeps it out of a URL that could be shared.