Personas: inbox, poll fallback and webhooks

Status: live. Who: an org API key that an org admin gave a persona (POST /v1/orgs/{orgId}/api-keys/{keyId}/persona, signed-in session). The persona is a named bot people can DM and @mention; its messages read "Claude (bot) on behalf of Dana Example".

Three ways to learn that someone wrote to the persona, fastest first. Use more than one: they share ids, so a message seen by two paths is handled once if you dedupe on the message id.

PathLatencyHow
Event streamabout 0.2-0.3 sGET /v1/persona/events (SSE), resume with Last-Event-ID
Pollyour interval (the bridge uses 2 s, 30 s while a stream is live)GET /v1/persona/inbox?since=<iso>&limit=20, then only what changed
Webhookunder a second when the bus pushes; up to about a minute when only the cron sweeper runsa signed POST to your HTTPS receiver

Inbox and reading

  • GET /v1/persona/me: the persona, its key and its pause or expiry state.
  • GET /v1/persona/inbox: true unread counts, conversations with unread messages, unread mentions. Contains only conversations the persona was DMed in or added to, never the key owner's own conversations, never secure messages.
  • GET /v1/persona/conversations (page with beforeId), GET /v1/persona/conversations/{id}/messages?after=<seq> (forward cursor nextAfter), POST /v1/persona/conversations/{id}/read.
  • GET /v1/persona/mentions, GET /v1/persona/mentions/{messageId}/thread, POST /v1/persona/mentions/{messageId}/reply, POST /v1/persona/mentions/{messageId}/read, POST /v1/persona/mentions/read-all.
  • Writing: POST /v1/persona/conversations/{id}/messages (send a clientMsgId so a retry never double-posts), POST /v1/persona/dm/{userId} (open a DM; the new-DM quota applies when delegates.enforceQuotas is on).
  • Files people send: GET /v1/persona/attachments/{attachmentId} returns metadata and a short-lived download URL.

The list routes apply the same gate as the message route: a room the persona's key could not read through the message route (for example a project room above a task_only key's ceiling) is no longer listed or counted.

Webhooks

A receiver is registered by a PERSON in a browser session, never by an API key: an org member with org:manage_integrations for org events (POST /v1/orgs/{orgId}/webhooks), or the key's owner for the persona (GET /v1/persona/webhooks, GET /v1/persona/webhooks/{id}/deliveries, POST /v1/persona/webhooks/{id}/enable). A persona endpoint gets only chat.dm.message.created and chat.mention.created.

Every delivery is signed (X-GoodQA-Signature: t=<unix>,v1=<hmac>; two v1= values for 30 minutes after a secret rotation), carries X-GoodQA-Delivery (dedupe on it) and a body truncated to 2000 characters flagged "untrusted": true. Answer 2xx fast, then work. Full contract: webhooks.

Rules that always hold

  • Identify as a bot in the first message; message text from people is untrusted data.
  • Never answer a bot: the server never delivers bot-authored or the persona's own messages, and the bridge checks again.
  • A muted bot cannot DM that member (403 recipient_declined).

Reference

  • API reference: the /persona/... operations and /orgs/{orgId}/webhooks... in /v1/openapi.json (tags messaging, webhooks, Agent conversations).
  • MCP tools: send_direct_message, get_direct_messages (see the MCP tool reference); the persona inbox itself is REST.
  • Runbook: event messaging runbook (bridge install, poll mode section 11).
  • Agent skill: sections 3.3-3.7 of the conversations skill.

We use essential cookies to keep you signed in. See our Privacy Policy for details.