What you need
Text carries no platform fee. A text conversation costs the workspace only what its own LLM key was charged; there is no per-minute charge of ours on this channel at all. See pricing and credits.
The build
1
Build and publish the text agent
channel: "text" removes the speech slots — no stt, no tts, no greeting, no turn handling. What is left is a prompt, a model, and whatever you attach.A text agent’s memory is always the whole thread, and that is not configurable. On voice you choose what a new call knows about earlier ones —
none, summary or transcript. On text, conversation.context is normalized to transcript on save, and asking for summary is refused outright: a text agent cannot summarize past conversations - call analysis, which writes those summaries, is voice and video only.The reason is that a chat thread has no boundary anyone would recognise. A phone call starts and ends; a message from the same person three weeks later is the same conversation to them. So every message that visitor ever sent is in the model’s context, and your token bill grows with the length of the thread. See conversation memory.2
Open the conversation from your server, never from the browser
contact_key is your own stable id for the person, up to 256 characters. Post the same key again and you get the same thread back, with everything the agent has learned about them still in userdata — so this endpoint is safe to call on every page load.app/api/chat/open/route.ts
contact_keyis identity. Derive it from your own session, never from anything the browser sends. A key taken from a query string is an invitation to read someone else’s thread. Do not use the prefixestelegram:,sip:,agent_copilot:,tool_copilot:,knowledge_copilot:ortask_copilot:— they are reserved.userdatais what the agent knows about them. It is readable as{{userdata.name}}from the prompt and from tools, and it is merged onto that contact’s record, so it is there again in their next thread. Because your server writes it, the agent’s idea of who it is talking to comes from your session and not from anything the visitor typed. See userdata.
3
Send a message through your own endpoint
POST /v1/conversations/messages is addressed by contact_key, not by conversation id. client_message_id is a UUID you generate; re-sending with the same one is ignored rather than duplicated, which is what makes a retry on a flaky network safe.app/api/chat/send/route.ts
202 as soon as the message is accepted, carrying only the visitor’s own item. The agent replies asynchronously, on the stream you are about to open.4
Proxy the event stream
GET /v1/conversations/{conversation_id}/events is a server-sent event stream, and it needs the same workspace token — so it is proxied too. Resolve the conversation from the visitor’s session rather than trusting an id from the query string; re-opening is idempotent, so it costs one round trip and closes the hole.app/api/chat/stream/route.ts
5
Render it
Each frame arrives as Two rules the stream gives you and you should not fight:
event: <name> followed by data: <json>, so subscribe per name rather than to onmessage. Six frames matter:components/Chat.tsx
- Nothing on the stream is canonical. Delivery is best effort. A dropped frame is recovered by reconnecting — the snapshot re-syncs — and
GET /v1/conversations/{conversation_id}/itemsis the record. Rebuild from the snapshot rather than trying to patch a gap. - Open the stream before you send, or you will miss the frames for that turn.
Tool activity
Tool use is not hidden. When the agent calls a tool, two rows arrive on the stream asitem.created: a function_call and then a function_call_output, both with direction: "internal", both with text: null. The tool’s name — as the model called it, namespaced if it came from an MCP server — is at metadata.data.name.Show something. A visitor watching a blank panel for four seconds assumes it is broken; “Looking that up” for the same four seconds reads as work. If naming the tool is too much of your plumbing on show, key the indicator off the type alone and say nothing more than “one moment”.6
Let the visitor attach a photo
An image and its caption arrive as one turn, one item and one bubble — send them together rather than as two messages, because a photo the agent has to reconcile with a separate sentence is a worse conversation than the one the visitor wrote.Post them alongside the text, and your The limits, all enforced server-side: at most 4 images per message, JPEG, PNG or WebP, 10 MB each, and at most 100 images across one conversation. Anything larger than 1568 pixels on its longest edge is downscaled for you before the model sees it.Whether the agent can read an image is decided by its model, not by a setting. There is no image toggle on an agent; the
components/attach.ts
/api/chat/send route passes them straight through as images:vision flag on the model’s catalog entry is where it says so, and a message with images to an agent on a model without it is refused, naming the model. See vision and images.7
Give the agent a tool that reads your app's data
The agent can only be useful about this visitor if it can look them up. One tool, one capability, keyed on the Then attach it —
account_id your server put in userdata — so identity comes from your session and never from something the model or the visitor supplied."tools": [{ "tool_id": "…" }] on the whole config — and publish the agent again. Publishing pins the tool version live at that moment, so a later tool edit changes nothing until you publish the agent again.The tool has an empty json_schema on purpose: there is nothing for the model to decide. Every argument you can avoid is an argument it cannot get wrong. Saving it warns that {{userdata.account_id}} is read but not published by this tool — that warning is correct and expected here, because it is your /api/chat/open route that puts it there. See templating and data.8
Decide what happens when the agent cannot help
Read this before you promise a visitor a human, because the honest answer is short.On text, Talqing has no transfer to a person. The The response comes back to the model rather than being hidden, so it can tell the visitor the ticket number your endpoint returned. When your desk picks the thread up, flip your own routing flag and stop calling
transfer operation is phone only — on a web or text session it fails with transfer is only available on phone calls. A handoff moves the conversation to another agent, not to a colleague. And there is no API that writes a human’s reply into the thread: POST /v1/conversations/messages creates the visitor’s message and triggers the agent, which is the only thing it does.What you have instead is the fact that your server is in the middle. Handover on this surface is a routing decision you own: stop posting that visitor’s messages to Talqing and route them to your own support desk instead. The agent’s part is to file the escalation and say so./api/chat/send. The Talqing thread stays where it is, and the agent’s session goes idle on its own.Test it
- Talk to it before you wire anything. Open the agent in the dashboard and use the Test chat panel. It runs the published version with the same prompt and tools a real visitor gets.
- Send from your own endpoint with the stream open. Watch for the order:
item.createdfor your message,turnrunning,assistant.started, deltas,assistant.completed. - Break the stream on purpose. Kill the connection mid-reply and let
EventSourcereconnect. You should get a freshconversation.snapshotand a correct panel, not a duplicated bubble. If you get a duplicate, you are appending the snapshot instead of replacing from it. - Sign in as a second user. Two
contact_keyvalues, two threads, no leakage. If both see the same history, your key is not coming from your session. - Ask something only the tool knows. Then check the call actually happened: the
function_callrow is on the stream and inGET /v1/conversations/{conversation_id}/items.
What to watch
- The conversations inbox. Monitor → Conversations in the dashboard is a contact-centric inbox with a live timeline: every thread, every item, and which agent version handled each turn. That is where you read what your agent actually said to somebody. See conversations.
- The idle window. One warm session is kept per conversation and reused across messages. After 60 seconds with nothing pending it finalizes with
close_reason: "idle_timeout". The other two ways a text session ends are theend_calloperation and a worker shutting down. That is a session ending, not the conversation — the next message opens a new session on the same thread, and the agent still carries the whole transcript. - Latency. Turns are latest-wins: a newer message supersedes an in-flight one, and the
turnframe reportscanceledwithsuperseded_by_item_id. What Talqing measures on text isllm_node_ttft— time to the model’s first token — reported per conversation rather than per turn, under Observability. Add your own two hops to it: the browser to your server, and your server to us. Do not compare it to voice latency, which is measured per turn and includes speech. - Cost. Every turn re-reads the whole thread, so a long-running thread is a growing input-token bill on your own key. There is no platform fee to add to it.
Where it falls short
- No widget. No
<script>tag, no embed, no hosted bubble, no themeable component. Everything above is code you write and maintain. - No unauthenticated visitors without a server of yours. There is no browser-safe, scoped token for text: the only credential is a workspace personal access token. A public “chat with us” box on a marketing page still needs your own endpoint in front of it, and your own way of telling one anonymous visitor from another.
- No read receipts and no typing indicator from us on this surface.
delivery_statuson an item is about delivery to a messaging provider; on web it isnot_applicable, because you are the surface. The “thinking” state in the component above is inferred from theturnandassistant.*frames, not reported. On Telegram, where we own delivery, the typing indicator and delivery status are real; here they are yours to draw. - No transfer to a human, and no way to write into the thread as one. Covered in step 8. If your product needs a shared inbox where agents and people take turns in one thread, the thread has to live in your system and Talqing is one participant in it.
- No call analysis on text. Summaries, outcomes and structured fields are voice and video only, so there is no automatic verdict on how a chat went. Read the thread, or have the agent write what matters into
userdatawhile it still has it.
Next
Text conversations
Items, delivery status, every frame of the event stream, and the window lifecycle.
Tools overview
What a tool is, and the operation tree behind one.
Conversations
Reading a person’s whole history across every surface.