Skip to main content

Unfurling links

Roam apps can receive matching links from new messages and attach rich preview cards without Roam fetching the destination. This is useful for incident, ticket, document, and deployment links whose best summary is available only to your app.

1. Register domains​

Domains are registered on your app, not on the Developer page itself:

  1. Open Roam Administration → Developer (gear icon → Settings).
  2. On the API Clients tab, click Edit next to your app under API Keys or OAuth Apps. (No app yet? Click Add API Key or Create OAuth App, then Create.)
  3. In the app's left sidebar, open the Webhooks tab and scroll to Link Unfurling Domains.
  4. Add up to five domains and click Save.

Exact hosts match only themselves. A leading wildcard matches exactly one additional label:

RegistrationMatchesDoes not match
status.example.comstatus.example.comwww.status.example.com
*.example.comstatus.example.comexample.com, a.status.example.com

Roam canonicalizes entries to lowercase ASCII hostnames. Schemes, paths, ports, IP addresses, localhost, public suffixes, and misplaced wildcards are rejected. Domain changes may take up to one minute to propagate in production.

Works with API Key and OAuth clients. Personal Access Token clients cannot register unfurl domains (or call chat.unfurl). OAuth installations inherit the domains configured on their parent OAuth app.

On the same app:

  1. Permissions tab: enable links:read (and links:write for step 3).
  2. Webhooks tab: enter your endpoint under Webhook URL, turn on chat.link.shared under Subscribed Events, and click Save.

Existing OAuth installations keep their old scopes until they re-authorize, so they won't receive link events or be able to call chat.unfurl until then.

See Configuring Webhooks for the rest of the Webhooks tab, or subscribe programmatically with /webhook.subscribe.

Each delivery uses the common webhook envelope. Its data.links list contains only URLs matching that installation's registrations, uniquely and in message order.

The event fires for newly created text and markdown messages only. It does not fire for edits or messages sent by that same app installation, preventing an unfurl response loop. Organization apps see member chats or public groups; personal installations need the owner in the chat — Chat.

3. Attach previews​

Request links:write and call POST /chat.unfurl with the event's chatId, messageTimestamp, and one or more exact URL keys:

{
"chatId": "8f3b9c2e-1a4d-4e7b-9c0a-2b6d1f5e3a7c",
"messageTimestamp": 1748906400000000,
"unfurls": {
"https://status.example.com/incidents/123": {
"title": "Incident 123",
"description": "Investigating elevated errors",
"siteName": "PagerDuty"
}
}
}

Every URL is checked against the message's current text and the app's current domain registrations. If any URL fails, the whole request is rejected with cannot_unfurl_url and no cards change. App cards replace Roam-generated cards for the same exact URL, preserve unrelated previews, and appear in message-link order. The latest valid app update wins.

favicon and image.url must be https display URLs. Roam's server does not fetch them; clients render them with the existing link-preview card.

Successful unfurls currently persist as an ordinary message edit (version bump). Clients may show an Edited label on the original message, and subscribers with chat:history receive a chat.message edit event. Suppressing the Edited badge for preview-only writes is planned as a follow-up.