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.
| Path | Latency | How |
|---|---|---|
| Event stream | about 0.2-0.3 s | GET /v1/persona/events (SSE), resume with Last-Event-ID |
| Poll | your 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 |
| Webhook | under a second when the bus pushes; up to about a minute when only the cron sweeper runs | a 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 withbeforeId),GET /v1/persona/conversations/{id}/messages?after=<seq>(forward cursornextAfter),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 aclientMsgIdso a retry never double-posts),POST /v1/persona/dm/{userId}(open a DM; the new-DM quota applies whendelegates.enforceQuotasis 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(tagsmessaging,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.