Skip to main content
A support agent replies when it is assigned to a thread. It reads the thread, replies to the customer, and reports agent status. For an agent that answers your team in Ask Sidekick, see internal agents. That uses different events and mutations, and nothing it writes reaches the customer.
Bring your own agent is currently in beta and free to use.
1

Give the agent an identity

Create a machine user under Settings → Machine users & API Keys.This example reads a thread, replies, reports status, and can hand off. Click Add API key and grant:
  • thread:read
  • thread:reply for replyToThread
  • thread:edit for agent status, done, and todo
  • thread:assign and thread:unassign
  • customer:read
Add more if the agent does more. A mutation without the right permission returns an error that names it.Copy the machine user ID from the URL (/settings/machine-users/mu_…).
2

Receive and verify events

Add an HTTPS POST endpoint. Create a target under Settings → Webhooks (Add webhook target). Subscribe to thread.thread_assignment_transitioned and customer-message events such as thread.email_received. Copy the signing secret from Settings → Request signing (Workspace HMAC Secret). The webhook target has no secret.
verifyPlainWebhook needs the raw request body, not parsed JSON. With Express, use express.text({ type: "*/*" }).
parsePlainWebhook skips the signature and checks shape only. See webhooks, request signing, and mTLS.
3

Decide which threads it acts on

In the handler, run only when the thread is assigned to your machine user:
Assign in the UI, or with a workflow under Settings → Workflows (channel, labels, customer tier, support hours). Workflows are UI-only. Assignment still arrives as thread.thread_assignment_transitioned. The payload includes previousThread.Ignore events your agent caused. For message events, skip when the author is your machine user. After a handoff, the assignee is no longer you, so the filter drops it.
4

Read the thread

Needs thread:read. customer, assignee, and labels are lazy-loaded. See the GraphQL SDK.Concatenate llmText on timeline entries for a prompt-ready thread.
llmText is null for entry types with nothing meaningful to render. Skip those entries.
You can also read thread.customer, thread fields, or the message already on payloads such as thread.email_received. To search Help Center articles and indexed documents, see searching knowledge.
5

Act on the thread

Reply with replyToThread (thread:reply). Works on API, CHAT, EMAIL, SLACK, and MS_TEAMS. Plain delivers on that channel as the machine user.
Always send both fields. textContent is the fallback; markdownContent is rendered in Plain, chat, and modern email. See reply to thread.
6

Report agent status

First Response, Next Response, and Investigating only show HANDED_OFF threads.

Caveats

If a user replies on a thread your agent marked HANDLED or IN_PROGRESS, Plain sets HANDED_OFF itself. On failure: createNote, updateThreadAgentStatus(HANDED_OFF), unassignThread, then markThreadAsTodo.

Also useful

Suggest a reply with addGeneratedReply (generatedReply:create). The customer sees nothing until a user sends it. Once you’ve suggested a reply on a thread, Ari stops drafting its own suggestions there. markdown max 5,000 characters. timelineEntryId must be a message from the customer or from a machine user (for example payload.email.timelineEntryId). See suggested replies.
Note (never sent to the customer):
Labels from Settings → Labels. removeLabels takes label IDs, not label type IDs. See labels.
Hand off with assignment:
Assign to a machine user from a classifier:
Filter in the handler (no assignment) for classifiers, notes, or a one-shot acknowledgement. Do not use this for an agent that handles support on its own.
Other events: thread.thread_created, thread.chat_received, thread.slack_message_received, thread.thread_status_transitioned. thread.thread_created plus thread.email_received fires twice for the first email; use isStartOfThread if you want one of them. API explorer

Resources