Skip to main content
If your product already has a chat assistant, keep it. When the bot can’t help, it hands the conversation to your team in Hinto. Your team answers from the Inbox, and your customer reads the answers in the same chat. One rule decides what your team sees:
  • Before the handoff, your code picks which turns of the bot’s chat to send. The default is the whole current session.
  • After the handoff, everything your customer sees goes into the Hinto thread: the customer’s messages and every message your bot shows them.
When the conversation is resolved, your bot answers again. The next handoff opens a new conversation. Your team sees the handoff at the top of the ticket: the bot’s name, its reason, its summary, and the earlier chat, collapsed. Only your team sees the reason and summary. Your bot’s later messages appear in the thread on your team’s side, under your bot’s name with an AI agent tag. If you don’t name the bot, your team sees “‘s AI agent”. There are two ways to connect:

In the browser

This path uses only @gethinto/browser-sdk. It needs the same setup as Hinto Signal: the publishable key and an authenticated token endpoint at /api/support-token. The handoff keeps the customer’s browser session, so investigations can cite what the customer did.

Connect with your bot’s name

Connect once per signed-in customer. The name you pass is the name your team sees. A name with nothing left after cleaning throws TypeError("invalid_bot_name").

Hand off

The customer’s own words open the ticket, so put their last message in the draft. Then send with a handoff. The reason is required. The summary and transcript are optional.
The SDK fits the handoff before it sends it:
  • It starts the transcript at startFromTurnId when you pass one. An id it can’t find keeps every turn.
  • It drops blank turns and keeps the newest 100.
  • It cuts each turn to 4,000 characters, the reason to 500, and the summary to 4,000.
  • It tells your team how many earlier turns it left out.
The fitted handoff is saved with the send, so a page reload sends the same request and never opens a second ticket. A send({ handoff }) answers like any other send. A blank reason answers invalid_draft. After the last conversation resolved, it starts a new conversation. While a conversation is still open, it sends a plain message and drops the handoff.

Let your model decide

If your bot uses a language model, give it a tool. The model decides when to hand off and writes the reason and summary. Your code still decides which turns go along.
Run the tool in the browser, where the conversation lives. With the Vercel AI SDK, declare the tool without execute, and call this from onToolCall.

Mirror what your bot says

Call shareBotMessage for every message your bot shows the customer. Your team then never contradicts something the bot already said. Each message is recorded once per id, so a retry or a reload never adds it twice.
A message shared before the runtime first reads the conversation, such as right after a reload, answers queued. It goes out once the read finds an open conversation, and it is dropped when the read finds none. Call it right after send({ handoff }) for the bot’s own “I’m passing you to the team” message. It waits for the new conversation, then appears after the customer’s message. A bot message that fails never holds back the customer’s messages.

Send the customer’s messages to your team

While your team has the conversation, the customer’s messages go to them through send(), not to your bot.

Show replies and status, and give control back

Your chat shows your team’s replies and the status from the snapshot. Teammate names stay hidden from the customer. snapshot.current.state is none before the first handoff, active while your team has the conversation, and resolved once they close it; then your bot answers again.
If your bot already tells the customer that a person will reply, turn off Hinto’s automatic reply in Settings > Auto-responses. Otherwise the customer gets two acknowledgments in a row. To show only replies from people, check each message with isSupportReply(message) from @gethinto/browser-sdk/conversation: the automatic reply has origin: "automatic".

Handle sign-out, errors, and help offers

  • Sign-out and account switches. Call support.updateAuth({ subject, getToken }) when another customer signs in, and support.updateAuth(null) at sign-out. The runtime moves capture to that customer, so do not call identify yourself. Call support.destroy() when your chat unmounts for good.
  • What the snapshot can be. snapshot.state is idle, loading, ready, sending, offline, retryable_failure, auth_required, error, or closed. Tell the customer when support is offline or failed and call support.retry(), and send them to sign in on auth_required. A send answers busy while a help offer’s message is on its way. While a send is sending, offline, or retryable_failure, its message is snapshot.pendingText and snapshot.draft no longer holds it, so empty your input as soon as you call send.
  • What a call answers. send, open, and retry answer ok, or a reason: invalid_draft (nothing to send), attachments_not_ready, busy, requires_start_new (call startNew first), unavailable, auth_required, error, or destroyed.
  • Help offers. Your chat can show Hinto Signal’s help offer next to the step where a customer is stuck, through support.offers. See Offer help without the React kit.

From your server

For a bot with no web page, your server talks to Hinto directly. It hands off, sends the end user’s messages, mirrors your bot’s messages, and reads your team’s replies. These requests carry no browser session, so the ticket cites no session evidence.

Create a secret key

Your server needs two values: The secret key is shown once. Keep it on your server only. Never put it in a website or an app, and never use the publishable key here. You can revoke it at any time in Settings > API keys.

The endpoints

Every path starts with /v1/native-support/server/apps/{appId}/users/{userId}. appId is your Signal app ID, HINTO_SIGNAL_APP_ID. userId is your own stable ID for the end user, the same value a browser token sends as sub. Send an Idempotency-Key header with a UUID on both POSTs that carry a customer message. Store the UUID with the end user’s message, so a retry sends the same key and never adds a second message. A retry with the same key and a different body answers 409 idempotency_conflict. A bot message is idempotent on its id instead. A missing or unknown key answers 401. A publishable key, or an older key made for another use, answers 403. An app ID that is not your organization’s Signal app answers 404, and a paused app answers 403 application_disabled. Each key may send 600 requests a minute, and each end user 120.

Hand off

The first request creates or updates the end user from user and opens the conversation. A user field you leave out keeps its stored value, and null clears it. The handoff takes botName, reason, summary, and transcript. Hinto fits it the way the browser runtime does: it drops blank turns, keeps the newest 100, clips each to 4,000 characters, and counts the turns it left out. A turn’s at that does not parse is dropped. A blank reason answers 400 invalid_message.

Send the end user’s messages

Mirror your bot’s messages

Read your team’s replies

To hear about a reply without polling, set a reply webhook (see Reply webhook). Otherwise poll the conversation, and wait conversation.status.refreshAfterMs milliseconds between polls, or 15 seconds when there is no status. Every poll returns the whole conversation. Send each support message to the end user once, by its id. When state is resolved, your bot answers again, and the next handoff opens a new conversation.

A complete server bot

The same four requests in TypeScript, with fetch:

Check your work

  1. Talk to your bot, then hand off. The ticket appears in your Hinto Inbox with the handoff at the top, and the earlier chat collapsed under it.
  2. Let your bot say something after the handoff. It appears in the thread under your bot’s name.
  3. Reply from the Inbox. The reply appears in your chat.
  4. Resolve the ticket. Your bot answers again, and the next handoff opens a new ticket.

Reply webhook

Hinto can call your backend when a teammate replies, so you can send a push or wake your bot. Set the URL in Settings > Hinto Signal, under Reply notifications. It must be a public https URL. The webhook has its own signing secret. Hinto shows it once, when you first save the URL and on each rotation; a rotation keeps the previous secret signing for 24 hours. After a reply is delivered, Hinto sends one POST with content-type: application/json:
sub is the subject your backend signed into the user’s token, and preview is the reply’s first 140 characters. A test send from Settings carries "test": true. Verify each request before you act on it. The Hinto-Signature header reads t=<unix seconds>,v1=<hex>, where the hex is HMAC SHA-256 of <t>.<raw body> keyed with your secret. During a rotation it carries one v1 per live secret: accept the event when any of them matches, and refuse a t more than 5 minutes old. Answer with any 2xx. Hinto retries a 429, a 5xx or a network error up to six times over about 51 minutes, so deduplicate by id (also in the Hinto-Event-Id header). Settings lists recent deliveries with their status codes.