Natiq pushes live changes (new messages, ticket updates, presence) over STOMP. The Inbox itself uses this feed, so anything you see move in the Inbox is available to you here. Events are not replayed after a disconnect, so plan to refetch when you reconnect.
Connect
| Item | Value |
|---|---|
| Native WebSocket | wss://api.cx.na6iq.com/ws (Saudi region: wss://api.sa.na6iq.com/ws) |
| SockJS fallback | https://api.cx.na6iq.com/ws (same path over HTTPS) |
| Protocol | STOMP over either transport |
| Auth | A CONNECT header Authorization: Bearer <accessToken> |
| Heartbeat | The dashboard asks for 10 seconds each way, but don't rely on server heartbeats to detect a dead connection: reconnect when the socket closes. |
The token is the same user token as for REST. See Authentication. The token is checked at CONNECT only. An open connection isn't closed when the token expires, but a reconnect needs a valid token — there is no refresh, so sign in again before reconnecting.
import { Client } from '@stomp/stompjs'
const client = new Client({
brokerURL: 'wss://api.cx.na6iq.com/ws',
connectHeaders: { Authorization: `Bearer ${accessToken}` },
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000,
reconnectDelay: 0, // reconnect yourself, with a fresh token
onConnect: () => {
client.subscribe('/user/queue/events', (frame) => handle(JSON.parse(frame.body)))
client.subscribe(`/topic/org.${orgId}.tickets`, (frame) => handle(JSON.parse(frame.body)))
},
})
client.activate()Natiq's dashboard tries the native WebSocket first and falls back to SockJS only if the native connection fails before it ever connected. Do the same if you need to support networks that block WebSockets.
Destinations
Replace {orgId} with the orgId from your login response. A subscription to another organisation's topic is refused.
| Destination | Carries |
|---|---|
/user/queue/events | Events for the signed-in user: tickets assigned to them or that they follow, their notifications, private notes on those tickets. |
/topic/org.{orgId}.conversations.{conversationId} | Everything on one conversation thread. |
/topic/org.{orgId}.conversations.{conversationId}.agents | The staff-only side of a conversation: who is typing. |
/topic/org.{orgId}.tickets | Every ticket change in the organisation, plus wait reasons and priority-case changes, and live-board hints. |
/topic/org.{orgId}.agents | Agent presence, and who has which conversation open, plus live-board refresh hints (LIVE_METRICS_CHANGED). |
/topic/org.{orgId}.dashboard | Dashboard metric invalidation and WhatsApp template status changes. |
Other /topic/org.… suffixes are refused. Subscribe to a conversation topic only for conversations you are watching, and unsubscribe when done.
The org-wide tickets topic carries every ticket change in the organisation; it is not filtered by inbox. If a ticket reaches you through both your user queue and the org topic, you receive it twice, so dedupe (see below).
Sending
You can send two small frames, both with a JSON body:
| Destination | Body | Meaning |
|---|---|---|
/app/conversations/{conversationId}/typing | {"typing": true} | You are typing in that conversation. Send false to stop. Typing turns off on its own after 10 seconds. |
/app/conversations/{conversationId}/viewing | {"viewing": true} | You have it open. The Inbox repeats it every 20 seconds as a heartbeat. Send false to stop; a viewer expires after 45 seconds without a fresh viewing signal. |
Visitors may send typing only for their own conversation, never viewing. Any other SEND destination is refused. These frames are best effort; do not queue them while offline.
Reconnect and catch up
Connections drop. A sensible loop:
- Reconnect with exponential backoff (the dashboard uses 1 second doubling up to 30 seconds).
- Sign in again if the token expired (REST answers 401).
- Resubscribe to every destination you had.
- Refetch what may have changed while offline, for example
GET /tickets. Missed events are not delivered afterwards.
Dedupe
The same event can arrive on more than one destination. Keys the Inbox uses:
| Event | Key |
|---|---|
MESSAGE_CREATED | message.id |
MESSAGE_UPDATED | message.id plus message.status |
PRIVATE_NOTE_CREATED | message.id |
NOTIFICATION_CREATED | notification.id |
| Ticket events | type, ticket.id and occurredAt |
Visitors
Web Chat visitors connect to the same endpoint with a visitor token, and may subscribe only to their own conversation topic. That is internal to the widget; you do not need it unless you are building your own chat client.
What is not covered
The event payloads are in Realtime events. We have not confirmed which STOMP protocol versions the server negotiates beyond what @stomp/stompjs and webstomp-client use; if your client does not connect, try one of those.