Get startedBuild a bot

Get started

Build a bot

A bot is an agent of type bot: routing gives it chats like any agent, it replies through the same agent API, and it hands a chat to a person by transferring it. You bring the brains; mlivechat brings the chats.

Create the bot and get a token

An admin opens Team › Chatbots › Add chatbot: name, picture, groups with a priority, chat limit, handover timeout. The token (mlcb_…) is shown once. New token issues another and revokes every earlier one at once (open sockets close with 4001). Turning the bot off refuses its token (403), closes its sockets (4003) and moves its chats to people.

Priority per group decides when the bot gets chats:

Priority Label in the app Meaning
first Bot first every new chat goes to the bot while it is under its chat limit
normal In turn the bot takes turns with the agents
last Fallback only when no person in the group can take the chat

Bots never take chats from the queue; a full bot simply stops getting new ones.

Authenticate

Every request carries the token:

ShellShell
curl -X POST -H "Authorization: Bearer $MLC_BOT_TOKEN" -H 'Content-Type: application/json' \
  -d '{}' https://app.staging.mlive.chat/v1/configuration/action/get_agent

Every method is one POST to /v1/agent/action/<method> (chats, events, tags, transfers, customers, routing status) or /v1/configuration/action/<method> (agents, groups, tags, canned responses) with a JSON body. The bot sees only chats assigned to it; anything else is not_found. Admin methods answer authorization.

Tell routing you are ready (a new bot starts not accepting):

ShellShell
curl -X POST -H "Authorization: Bearer $MLC_BOT_TOKEN" -H 'Content-Type: application/json' \
  -d '{"status":"accepting_chats"}' https://app.staging.mlive.chat/v1/agent/action/set_routing_status

Hear about chats: WebSocket or webhooks

Pick one. Both carry the same payloads.

WebSocket (no public URL needed)

  1. POST /v1/ws_ticket with the token → {ticket, url} (single use, 30 s).
  2. Open wss://<host>/v1/ws?v=1&ticket=<ticket> (bots send no Origin).
  3. Send {"request_id":"1","action":"login","payload":{}} first.
  4. Send {"action":"ping"} every 25 s; reconnect with a new ticket on close (backoff 0.5 s → 30 s).

Pushes a bot cares about:

Push When What to do
user_added_to_chat (agent_id = you) routing gave you a chat greet, or answer the opening message (get_chat)
incoming_event a message or system message in your chat answer author_type: customer messages; see postback and system_message_type
chat_transferred the chat left you (take over, timeout) or came to you stop / start answering
chat_deactivated the chat closed forget it
GoGo
c, _, _ := websocket.Dial(ctx, "wss://app.mlive.chat/v1/ws?v=1&ticket="+ticket, nil)
c.Write(ctx, websocket.MessageText, []byte(`{"request_id":"1","action":"login","payload":{}}`))
for {
	_, raw, err := c.Read(ctx)
	if err != nil { return err } // 4001: token revoked; 4003: bot turned off
	var f struct{ Action string; Payload json.RawMessage }
	json.Unmarshal(raw, &f)
	// switch f.Action { case "user_added_to_chat", "incoming_event": … }
}

Webhooks

A bot's webhooks only carry events about its chats (except customer_visit_started, below):

Action Sent when Payload
incoming_chat routing gives the bot a chat {chat_id, seq, thread, agent_id}
incoming_event a visitor message or system message in a chat the bot has now (not agents' or its own messages) {chat_id, seq, thread_id, event}
chat_transferred a chat the bot has or had in this thread changes hands {chat_id, seq, thread, transferred_to, queue}
chat_deactivated such a chat closes {chat_id, seq, thread, reason}
customer_visit_started a visitor opens your site with the widget and has no open visit (once per visit: reloads, more tabs and new pages in the same visit send nothing; a visit ends 60 s after the last tab closes) {visit_id, visitor}

customer_visit_started is the one action not tied to the bot's chats: there is no chat yet, so every bot subscribed to it hears every new visit. Organization webhooks (Settings → Webhooks) can subscribe to it too.

Each POST has the envelope {webhook_id, secret_key, action, organization_id, payload} and an X-Mlc-Signature: t=<unix>,v1=<hex> header: HMAC-SHA256 with the webhook's secret (shown once at creation) over "<t>." + raw body. Answer 2xx within 10 s; failures are retried 3 times within about a minute. Deliveries are unordered; use seq.

Verify every request before trusting it, and reject a t older than 5 minutes:

GoGo
func verify(secret string, r *http.Request, body []byte) bool {
	var t int64
	var sig string
	for _, part := range strings.Split(r.Header.Get("X-Mlc-Signature"), ",") {
		if v, ok := strings.CutPrefix(part, "t="); ok { t, _ = strconv.ParseInt(v, 10, 64) }
		if v, ok := strings.CutPrefix(part, "v1="); ok { sig = v }
	}
	if d := time.Since(time.Unix(t, 0)); d > 5*time.Minute || d < -5*time.Minute { return false }
	mac := hmac.New(sha256.New, []byte(secret))
	fmt.Fprintf(mac, "%d.", t)
	mac.Write(body)
	want, err := hex.DecodeString(sig)
	return err == nil && hmac.Equal(mac.Sum(nil), want)
}
ShellShell
# The same check in a shell, for debugging (body in body.json, header value in $SIG):
t=${SIG#t=}; t=${t%%,*}; v=${SIG#*v1=}
printf '%s.' "$t" | cat - body.json | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | grep -q "$v" && echo ok

Reply

ShellShell
curl -X POST -H "Authorization: Bearer $MLC_BOT_TOKEN" -H 'Content-Type: application/json' \
  https://app.staging.mlive.chat/v1/agent/action/send_event -d '{
    "chat_id": "'$CHAT'",
    "event": { "type": "message", "text": "Orders ship within 1 business day.", "custom_id": "order-faq-1" }
  }'

custom_id is any string you choose; retrying with the same one never sends twice. Over the socket the same message goes in {"action":"send_event","payload":{"chat_id":…,"client_message_id":…,"event":{…}}}. Show you’re working on it with send_typing_indicator {chat_id, is_typing} (the same method on the socket). Message text is 1 to 4000 characters. A bot has the agent rate limit: 30 messages per 10 s.

Rich messages: quick replies

Add up to 10 buttons to a visible message:

ExampleJSON
{ "chat_id": "…", "event": { "type": "message", "text": "What can I help with?",
  "quick_replies": [ { "title": "Shipping", "postback": "faq:shipping" },
                     { "title": "Returns",  "postback": "faq:returns" } ] } }

title is 1 to 40 characters, postback 1 to 200. The widget shows the buttons under the newest message until the visitor writes; a tap sends title as the visitor's message with postback attached, which you get as event.postback. Agents see the buttons read-only in the agent app. Treat a postback as visitor input: it is not signed.

Hand over to a person

ShellShell
curl -X POST -H "Authorization: Bearer $MLC_BOT_TOKEN" -H 'Content-Type: application/json' \
  https://app.staging.mlive.chat/v1/agent/action/transfer_chat \
  -d '{"id":"'$CHAT'","target":{"type":"group","ids":["0"]}}'
  • target is a group (its accepting people, least loaded first; never another bot) or one agent ({"type":"agent","ids":["<agent id>"]}, who must be online and under their limit).
  • Nobody accepting in the group: the chat waits in that group's queue and the visitor sees their position.
  • To leave the agent a note the visitor never sees, send it first with send_event and "visibility": "agents".
  • The thread is tagged bot-transfer. Reports › Overview counts these as Bot handovers.

Two more ways a chat leaves the bot, both tagged bot-transfer:

  • The visitor asks. While a bot has the chat the widget shows Talk to a human. The bot gets a system message with system_message_type: "handover_requested" (socket incoming_event, and the incoming_event webhook). Transfer when you see it. If the bot hasn't within its handover timeout (default 20 s, set per bot), the server transfers to the chat's group itself.
  • A person takes over. Agents see a Take over button on any chat a bot is handling. The bot gets chat_transferred (socket and webhook) and can no longer write in the chat (404).

Checklist before going live

  • The token lives in your secret store, not the code; rotate it from Team › Chatbots if it leaks.
  • Webhook signatures are verified and old timestamps rejected.
  • Reconnects use a new ticket each time and back off; 4001/4003 stop the bot.
  • client_message_id is reused on retry.
  • The bot hands over on handover_requested, and when it isn't sure.
  • The chat limit fits what your service can answer at once (1 to 500, default 50).