Send messages and consume realtime events
One visitor send is one logical operation. The browser generates a stable clientOperationId; Qanivo returns a canonical messageId and sequence. The POST response and the matching history/SSE event reconcile to that same operation instead of creating a second message.
Send a message over HTTP
POST /api/integrations/v1/widget/messages HTTP/1.1
Origin: https://www.example.invalid
X-Qanivo-Widget-Version: 1.0
Authorization: Bearer <YOUR_ACCESS_GRANT>
Idempotency-Key: <YOUR_CLIENT_OPERATION_ID>
Content-Type: application/json
{
"clientOperationId": "<YOUR_CLIENT_OPERATION_ID>",
"body": "Hello from the website",
"contentType": "text",
"locale": "en-US",
"pageContext": { "section": "pricing" }
}
The accepted response includes clientOperationId, messageId, sessionId, conversationId, sequence, and a pending/queued state. Keep the operation identifier with the host UI until a terminal outcome is received.
Realtime stream and resume
The Widget opens the SSE route with the active grant. On reconnect it sends both Last-Event-ID and the afterSequence query parameter when available:
GET /api/integrations/v1/widget/events?afterSequence=<YOUR_LAST_SEQUENCE> HTTP/1.1
Origin: https://www.example.invalid
X-Qanivo-Widget-Version: 1.0
Authorization: Bearer <YOUR_ACCESS_GRANT>
Last-Event-ID: <YOUR_LAST_EVENT_ID>
Accept: text/event-stream
Events have a stable SSE ID and a monotonically increasing per-session sequence. The runtime de-duplicates by event/message identity, applies bounded replay, and uses bounded reconnect backoff. A replay reset or realtime terminal event requires explicit application handling and must not loop forever.
Typical event families include message.accepted, message.queued, message.retrying, message.retry.exhausted, message.received, delivery-state changes, typing, agent attribution, session changes, ban/revocation, navigation, screenshot requests, and connection disablement.
widget.on('message.queued', ({ message }) => {
setMessageState(message.clientOperationId, 'queued');
});
widget.on('message.retrying', ({ message, attempt }) => {
setMessageState(message.clientOperationId, 'retrying', attempt);
});
widget.on('message.retry.exhausted', ({ message, error }) => {
setMessageState(message.clientOperationId, 'failed', error.code);
});
widget.on('session.expired', () => setTransportState('terminal'));
widget.on('error', (error) => {
if (error.code === 'realtime_terminal_failure') setTransportState('terminal', error.code);
});
Event names are compatibility-facing examples; use the package’s exported event types for the installed version. Unknown informational events may be ignored only when the declared compatibility policy permits it. Security and action commands require explicit support.
History reconciliation
After a reconnect, use the bounded history endpoint to fill a replay gap:
GET /api/integrations/v1/widget/messages?afterSequence=<YOUR_LAST_SEQUENCE>&limit=50 HTTP/1.1
Origin: https://www.example.invalid
X-Qanivo-Widget-Version: 1.0
Authorization: Bearer <YOUR_ACCESS_GRANT>
Merge by messageId, then by clientOperationId when the canonical message has not arrived yet. Never append a second message solely because the POST response and SSE event arrived in a different order.