Quickstart: add live chat to your site
This guide puts a chat bubble on your website, tells the inbox who your signed-in users are, and has you answer the first message from the dashboard, the Ha Tang mobile apps or your own server. At the end you add the same chat to your iOS, Android, Flutter or React Native app.
Before you start
- A Ha Tang account at app.hatang.dev with the Support module turned on (Businesses and modules).
- A website you can edit, with access to its HTML layout or a tag manager.
- To change Support settings you need to be an owner or admin, or a member with Manage access to Support (Team and roles).
1. Copy the snippet
- In the dashboard, open Support → Channels and choose your website. To add another one, click Add channel and pick it under Websites.
- In the Install card, click Copy snippet.
The snippet is two lines. The first sets your site id (it starts with site_), the second loads the widget from the chat gateway:
<script>window.$hatang=window.$hatang||[];window.HATANG_SITE_ID="site_123...";</script>
<script src="https://chat.hatang.dev/widget.js" async></script>
2. Add it to your site
Paste the snippet just before </body> on every page, or in the layout that every page shares. The script loads asynchronously and does not block your page.
Reload your site: the chat bubble appears in the corner. Back in the dashboard, the Install card switches to Installed and shows the host it was seen on.
Using WordPress, Shopify, Haravan, LadiPage, Sapo, Google Tag Manager, Webflow or React? The widget install guide has steps for each, and the WordPress plugin needs no code at all.
The widget only starts on your website's domain and its subdomains. To test on http://localhost:3000 or a preview deployment, add that origin under Extra allowed origins on the website's page. Other origins are refused with origin_not_allowed.
3. Identify signed-in users
Without any code, every visitor is an anonymous guest. When a user is signed in on your site, pass their details to the widget so your team sees who they are talking to:
<script>
window.$hatang = window.$hatang || [];
$hatang.push(["identify", {
user_id: "42",
name: "Nguyễn Văn An",
phone: "0901234567",
hash: "<%= hatangHash %>"
}]);
</script>
$hatang.push works before and after the widget has loaded, so you can put this anywhere after the first line of the snippet.
The hash proves that the user id came from your server and not from someone typing it in the browser console. Without it, the details are shown as unverified and are never merged with another customer's history. With a valid hash, the inbox treats the user id as verified and joins all their sessions, on every device, into one customer.
Compute hash on your server as the HMAC-SHA256 of the user id, keyed with the site's Identity secret (on the website's page, Rotate & reveal), in lowercase hex:
- Node.js
- Python
- PHP
import { createHmac } from "node:crypto";
export const hatangHash = (userId) =>
createHmac("sha256", process.env.HATANG_IDENTITY_SECRET).update(String(userId)).digest("hex");
import os
from hatang import identity_hash # pip install hatang
def hatang_hash(user_id) -> str:
return identity_hash(str(user_id), os.environ["HATANG_IDENTITY_SECRET"])
$hash = hash_hmac('sha256', (string) $userId, getenv('HATANG_IDENTITY_SECRET'));
Anyone with the identity secret can sign in to the chat as any of your users. Never put it in page JavaScript, a mobile app or a public plugin; send only the computed hash to the browser.
The identity verification guide explains merging, secret rotation and the syntax for every SDK. The JavaScript API lists the other commands, such as open, setLocale and on.
4. Reply from the inbox
Open your site in a private window and send a message from the widget. It arrives in Support → Inbox in real time. Type a reply and the visitor sees it at once in the widget.
Your team can also answer from the Ha Tang apps for iPhone, Android and Mac, which receive push notifications for new messages.
To answer from your own systems instead, use the REST API with a key that has the chat:write scope. List open conversations, then post a message as your team:
- cURL
- Node.js
- Python
- PHP
curl "https://api.hatang.dev/chat/conversations?status=open&limit=20" \
-H "Authorization: Bearer $HATANG_API_KEY"
curl -X POST https://api.hatang.dev/chat/conversations/cnv_123.../messages \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reply-cnv_123-1" \
-d '{ "content": "Hi An, your order ships today." }'
import { Hatang } from "@hatang/sdk";
const hatang = new Hatang(process.env.HATANG_API_KEY, { baseUrl: "https://api.hatang.dev" });
const { data: open } = await hatang.chat.conversations.list({ status: "open", limit: 20 });
const conversation = open.data[0];
const { data, error } = await hatang.chat.conversations.send(
conversation.id,
{ content: "Hi An, your order ships today." },
{ idempotencyKey: `reply-${conversation.id}-1` },
);
from hatang import Hatang
hatang = Hatang() # reads HATANG_API_KEY
open_ = hatang.chat.conversations.list(status="open", limit=20)
conversation_id = open_["data"][0]["id"]
message = hatang.chat.conversations.send(
conversation_id,
{"content": "Hi An, your order ships today."},
idempotency_key=f"reply-{conversation_id}-1",
)
print(message)
<?php
$client = new GuzzleHttp\Client([
'base_uri' => 'https://api.hatang.dev',
'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
]);
$open = json_decode((string) $client->get('/chat/conversations', ['query' => ['status' => 'open', 'limit' => 20]])->getBody(), true);
$conversationId = $open['data'][0]['id'];
$res = $client->post("/chat/conversations/{$conversationId}/messages", [
'headers' => ['Idempotency-Key' => "reply-{$conversationId}-1"],
'json' => ['content' => 'Hi An, your order ships today.'],
]);
The message call answers 201:
{ "object": "chat_message", "id": "msg_123...", "seq": 7, "conversation_id": "cnv_123...", "duplicate": false }
To react to new visitor messages without polling, subscribe a webhook to chat.message.created. The Support REST API guide covers assigning, tagging, private notes, customers and orders.
5. Add chat to your mobile apps
The native SDKs talk to the same gateway as the widget, so messages from your app land in the same inbox and a verified user keeps one history across web and mobile.
- In Support → Channels, click Add channel, then Mobile apps, and Create app key. The key starts with
hak_and is shown only once. - Install the SDK for your platform and configure it with your site id and the app key.
- Call
identifywith the same user id andhashas on the web. - Optionally upload your Firebase credentials so the app receives push notifications.
| Platform | Guide |
|---|---|
| Flutter | Flutter SDK |
| iOS (Swift) | iOS SDK |
| Android (Kotlin) | Android SDK |
| React Native / Expo | React Native SDK |
| React / Next.js (web) | React component |
The app key is not a secret in the way an API key is (it ships inside your app), but you can revoke it and issue a new one at any time. Never ship an ht_ API key or the identity secret in an app.
What can go wrong
| Symptom | Cause | Fix |
|---|---|---|
No bubble, origin_not_allowed in the browser console | The page is not on the website's domain | Add the origin under Extra allowed origins |
No bubble, product_disabled | The Support module is turned off | Ask an owner or admin to turn it on |
| No bubble on some pages | Display rules on the website's page hide it there | Check where the widget is set to show |
| A user shows as unverified | hash is missing or does not match | Compute it from the exact user id string, with the current identity secret |
invalid_app_key in a mobile app | Wrong key, or a key of another site | Copy the app key of the right website again |
Next steps
- Support overview: channels, routing, tickets and the AI assistant
- Widget JavaScript API
- Identity verification
- SDK overview