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
In Roam Administration → Developer → API Client → Webhooks, add up to five Link Unfurling Domains. Exact hosts match only themselves. A leading wildcard matches exactly one additional label:
| Registration | Matches | Does not match |
|---|---|---|
status.example.com | status.example.com | www.status.example.com |
*.example.com | status.example.com | example.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.
2. Subscribe to link events
Request links:read, configure a webhook URL, and subscribe to
chat.link.shared. 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.
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.