Skip to main content

Realtime

Every chat client talks to the chat gateway at https://chat.hatang.dev over one WebSocket protocol. This page explains how a session works from start to finish, so you can debug the widget, understand what the SDKs do, or write your own client. The frame-by-frame details are in the realtime protocol reference and the HTTP endpoints in the chat gateway reference.

Pick the right interface​

You want toUse
Put chat on a websiteThe web widget; it speaks the protocol for you
Put chat in a mobile appA native SDK (Mobile apps)
Build your own customer-facing chat UIThe visitor protocol described below
Read and answer conversations from your server, bot or CRMThe Support REST API with an API key, plus webhooks

The agent side of the gateway serves Ha Tang's own dashboard and apps for iPhone, Android and Mac. It needs an employee's sign-in, not an API key, so server integrations should use the REST API instead.

The visitor session​

A visitor is one browser or one app install on one site. The session runs in five steps.

1. Bootstrap​

The client calls POST /widget/v1/sites/{siteId}/bootstrap once when it starts.

  • Browsers are checked by Origin. The page must be on the site's domain, a subdomain of it, or one of the site's extra allowed origins. Anything else gets 403 origin_not_allowed.
  • Native apps send their app key in the x-hatang-app-key header and an app.install_id (a stable id per install) in the body. No origin is needed.

The body can carry the stored token (to resume the same visitor), page or screen, and locale. The response contains:

FieldWhat it is
visitor_tokenThe visitor's credential. Store it (a cookie or local storage on the web, the keychain in an app) and send it back next time
visitorThe visitor record
configThe site's widget settings, campaigns and contact buttons
availabilityWhether the team is online or within office hours
conversation, timelineThe active conversation, if any, and its last 100 visible items
ws_urlThe socket address to open, for example wss://chat.hatang.dev/ws?site=site_...
protocol_versionCurrently 1

Other HTTP calls in the session send the token in the x-visitor-token header.

2. Connect and authenticate​

Open ws_url exactly as returned and send an auth frame within 5 seconds. Every frame is JSON with a type t, data d, version v and an optional id that the server echoes in its answer.

const ws = new WebSocket(boot.ws_url);
ws.onopen = () => ws.send(JSON.stringify({
t: "auth", v: 1,
d: { token: boot.visitor_token, site_id: "site_...", page: { url: location.href, title: document.title } },
}));
ws.onmessage = (e) => {
const frame = JSON.parse(e.data);
if (frame.t === "ready") render(frame.d.conversation, frame.d.timeline);
if (frame.t === "message") append(frame.d);
};

The server answers with ready, which carries the visitor, the active conversation, its timeline and the team's availability. After that, both sides send ping every 25 seconds.

3. Send messages​

Each outgoing message gets a client_id that the client generates:

{ "t": "message.send", "id": "req_1", "v": 1, "d": { "client_id": "c_9f2", "content": "Shop còn hàng không ạ?" } }
  1. Show the message as pending and wait up to 10 seconds for message.ack, which returns the message id, its seq and the conversation_id. The first message of a visitor creates the conversation.
  2. On a timeout or an error, keep the bubble marked as failed and retry with the same client_id. The server recognizes it and acknowledges the original message, so a retry never posts twice.
  3. The message itself also comes back as a message frame. Deduplicate by id.

Visitors can send 5 messages per 10 seconds and 60 per minute. Files are uploaded first with POST /widget/v1/sites/{siteId}/uploads (up to 10 MB), then sent by id in attachments.

4. Receive​

While connected, the visitor receives:

FrameWhen
messageAn agent, the AI assistant or the system posts in one of the visitor's conversations
typingAn agent is typing
readThe agent side has read up to a seq
availabilityThe team goes online or offline
conversation.updateA conversation's status, unread count or handling agent changes
eventEvents the visitor should see, such as a status change or a handoff to a person
sessionThe visitor was merged into another one by a signed identify: store the new token

The client reports what the visitor does with typing, read, page (browsers) or screen (apps), and identify when the visitor signs in (Identity verification). Apps also send app.state when they go to the background, so replies reach the person as push notifications instead.

5. Reconnect without losing anything​

Every message and event in a conversation has a seq that only grows. Keep the highest seq you have seen. After a reconnect, send resume {conversation_id, after_seq} and the server replays everything you missed from the database. Earlier history is paged over HTTP with before_seq.

When the socket closes, look at the close code:

CodeMeaningWhat to do
4001No auth within 5 secondsReconnect and send auth first
4003Authentication refused or revokedStop; drop the token and bootstrap a new visitor
4004The client's protocol is too oldBrowsers reload; apps ask the user to update
4005A session frame carried a new tokenReconnect at once with the new token
OtherNetwork or gateway restartReconnect with backoff, min(30 s, 500 ms · 2^n) plus jitter

The agent side​

The dashboard and the Ha Tang apps use the same gateway as employees:

  • Socket. An agent connection authenticates with a short-lived agent token. The dashboard gets one from the signed-in session and it is accepted only from the dashboard's origin; the mobile and Mac apps get theirs when the employee signs in. Agents then subscribe to conversations, receive message, conversation.upsert, typing and presence frames, and send messages, notes, assignments, status changes, tags and reminders.
  • HTTP. The apps also call /agent/* endpoints, for example GET /agent/conversations for an inbox snapshot, POST /agent/uploads for attachments (10 MB, videos 25 MB; a .mov is stored as MP4), POST/PATCH/DELETE /agent/canned for the agent's own canned responses, and /agent/conversations/{id}/scheduled plus /agent/scheduled/{id} for scheduled messages (their changes arrive to every agent as the scheduled frame). These are limited to 300 reads and 60 writes per minute per person.
  • Permissions. The token carries the employee's role. People who can only view Support receive the live inbox but cannot act in it (Team and roles).

Because these tokens belong to a person and expire within an hour, they are not meant for integrations. To automate the inbox, call the Support REST API with an API key and subscribe to webhooks.

Troubleshooting​

SymptomLikely cause
origin_not_allowed on bootstrapThe page's domain is not the site's domain, a subdomain of it, or an allowed origin. Add it under Extra allowed origins on the website's page in Support → Channels
invalid_visitor_tokenThe token expired, was reset, or belongs to another site. Bootstrap again without it
product_disabledThe Support module is off for the business
The socket closes with 4001The client sent something before auth, or took longer than 5 seconds
Messages appear twiceThe client does not deduplicate the message echo by id
Gaps after a network dropThe client does not send resume with its last seq

Next steps​

View this page as Markdown