Sender Profiles
Apps can control the name and avatar a chat message appears with. The optional
sender object on message-sending endpoints covers two independent concepts:
{
"sender": {
"id": "support-bot",
"name": "Support (EMEA)",
"imageUrl": "https://example.com/support-emea.png"
}
}
sender.name/sender.imageUrl— per-message display overrides. The supplied name and image are stored on that one message and rendered in place of the author's profile. They are display data, not an identity: they never create or modify anything beyond the message itself.sender.id— a configured bot persona. If the id matches a persona you configured for your app in Developer settings, the message is authored by that persona's stable, @-mentionable identity. Any other value is accepted and ignored.
You can use either mechanism on its own or combine them.
Per-message display overrides
Pass sender.name and/or sender.imageUrl on
/chat.post,
/chat.postEphemeral, or
/chat.startStream to control how that single
message is displayed:
{
"groupId": "88bebce7-6cbb-4666-96f9-5c02d73e6661",
"text": "Build **#4021** passed",
"sender": { "name": "CI · frontend", "imageUrl": "https://example.com/ci.png" }
}
Rules:
nameis capped at 128 characters (UTF-16 code units). Longer values return 400.imageUrlmust be an absolute HTTP(S) URL with a host. Anything else returns 400.- Both fields are optional and independent. An omitted field falls back to the author's profile (the app's configured display, or the selected persona's) at render time.
- Overrides are immutable: they are snapshotted onto the message at send time. Renaming your app or reconfiguring a persona later never retroactively changes past messages, and sending with an override never renames your app or a persona.
Display overrides are presentation, not authentication. Clients always retain
the real author (bot badge and via-app attribution), and API consumers should
rely on the message's userId / userType — never the display name — for
attribution and bot-loop prevention. See
Identity & Principals.
Configured bot personas
A persona is a stable secondary identity for your app — for example one app
posting as support-bot, onboarding-bot, and sales-bot. Unlike a display
override, a persona is a real address in the workspace: users can @-mention
it, and its profile (name and avatar) is managed centrally in your app's
configuration.
Configuring personas
Personas are configured on the app, not through the message API:
- Open Roam Administration → Developer and edit your app (API key or OAuth app).
- In the bot configuration section, click Add Bot Persona.
- Give the persona a code (its
sender.id), a display name, and optionally an avatar image.
Constraints:
- Up to 10 personas per app.
- Codes are 1–16 characters, unique per app (case-insensitive), and stored
lowercase. The code
_is reserved for the app's root identity. - Display names are 1–128 characters.
Persona addresses are provisioned when you save the configuration (for OAuth apps, across existing installs shortly after saving). Removing a persona stops it from being selectable for new messages; messages it already sent keep rendering with its identity.
Sending as a persona
Pass the persona's code as sender.id:
{
"groupId": "88bebce7-6cbb-4666-96f9-5c02d73e6661",
"text": "A new ticket was assigned to you.",
"sender": { "id": "support-bot" }
}
- Matching is trimmed and case-insensitive.
- Selection is lookup-only: sending never creates or updates persona addresses. Provisioning happens only through configuration.
- An omitted, empty, or
_id posts as the app's root identity. - You can combine
sender.idwithname/imageUrl: the message is authored by the persona, with the supplied display override applied to that message only. The persona's configured profile is unchanged.
Unknown ids are accepted and ignored
A sender.id that does not match a configured persona — including malformed
or over-length values — is not an error. The request succeeds, the message
is authored by the app's root identity, and any supplied name/imageUrl is
still applied as a per-message display override.
This keeps integrations that put dynamic data in sender.id (dates, ticket
ids, phone numbers) working, without minting a new bot identity per value. If
you want messages attributed to a distinct, mentionable identity, configure it
as a persona; merely supplying an id never creates one.
Authorization is always the app root
Personas affect authorship and display only. Chat and group access,
attachment ownership, and chat.update / chat.delete ownership always
resolve through your app's root identity — the same one used when you send
without a sender. Adding your app to a channel is sufficient for all of its
personas to post there; personas cannot be granted or denied access
individually, and a persona cannot be added to a channel as a member.
Endpoint support
| Endpoint | sender.id | sender.name / imageUrl |
|---|---|---|
/chat.post | Selects a configured persona | Per-message display override |
/chat.postEphemeral | Selects a configured persona | Per-message display override |
/chat.startStream | Selects a configured persona (fixed for the stream's lifetime) | Per-message display override |
/chat.typing | Selects a configured persona | Rejected (400) — typing shows the persona's configured profile |
/chat.update, /chat.delete | Rejected (400) — sender is derived from the original message | Rejected (400) |
/v0/chat.sendMessage (deprecated) | Same semantics as /chat.post | Same semantics as /chat.post |
Personal access tokens do not support sender. A PAT always posts as the
personal bot provisioned with the token; any sender field on a PAT request
returns 400 (access_mode_not_supported). OAuth apps installed with personal
access do support sender, with personas configured on the app.
Reading sender display from the API
Messages sent with a display override carry it in read payloads, so mirrors and bridges can render the same name/avatar users saw:
- v1 (
/chat.history,/chat.search,/chat.link.resolve,/chat.listmessage previews, and thechat.messagewebhook): an optionalsenderobject withnameand/orimageUrl. Present only when the stored message carries an override. - v0 surfaces expose the same data as
senderDisplayInfo.
The field is additive: userId (v1) remains the authoring identity — the
app's root or a configured persona — and resolves through
user.info as usual.
{
"userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
"userType": "bot",
"text": "Build **#4021** passed",
"sender": { "name": "CI · frontend", "imageUrl": "https://example.com/ci.png" },
"timestamp": 1718900000000000
}
Behavior change (August 2026)
Previously, sending with an arbitrary sender.id created a new bot
address for that id on the fly, and re-sending could rename it. This minted
junk identities (dates, ticket numbers, phone numbers as ids) and is no longer
the case:
| Before | Now |
|---|---|
Unknown sender.id created a new bot address on send | Accepted and ignored; message is authored by the app root |
sender.name / imageUrl renamed the sender's stored profile | Applied as an immutable per-message display override |
| Configured persona ids selected the persona | Unchanged — still selects the persona (lookup-only) |
PAT with any sender → 400 | Unchanged |
Requests that relied on minting a durable, mentionable identity from an arbitrary id still return 200, but the message is authored by the app root with the supplied display applied per-message. To keep a distinct stable identity, configure it as a persona. Identities created under the old behavior remain valid for rendering historical messages, but can no longer be selected for new sends unless configured.