Skip to main content

Block Kit Messages

Roam supports a subset of Slack's Block Kit for sending rich, structured messages via the chat.post API endpoint. Block Kit lets you compose messages with headers, formatted text sections, buttons, dividers, and contextual metadata — going beyond plain markdown.

Block Kit messages sent via chat.post are also returned by chat.history, with the same blocks and color fields.

info

Roam implements a focused subset of Slack's Block Kit. You can prototype your layouts in Slack's Block Kit Builder — just be aware that only the block types listed below are supported.

Quick Example

curl -X POST https://api.ro.am/v0/chat.post \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chat": "G-757dfe66-37b4-4772-baa5-8c86ec68c176",
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "Deployment Complete" }
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Service *api-gateway* deployed to production.\nVersion: `v2.4.1`"
}
},
{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "Deployed by @rob · 2 minutes ago" }
]
}
],
"color": "good"
}'

Basic Block Kit message

Supported Block Types

Block TypeDescriptionKey Fields
headerLarge bold headingtext (plain_text only)
sectionText block with optional buttontext (plain_text or mrkdwn), optional accessory
contextSmall metadata textelements (array of text objects)
dividerHorizontal rule(none)
actionsRow of interactive buttonselements (array of 1–8 buttons)

Displays a large, bold text heading. Only supports plain_text (no markdown formatting).

{
"type": "header",
"text": { "type": "plain_text", "text": "Weekly Status Report" }
}

Section

The primary content block. Supports both plain_text and mrkdwn text, and can include an optional button accessory displayed to the right of the text.

{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Pull Request #482*\n`feature/user-onboarding` → `main`\n3 files changed, 47 insertions(+)"
},
"accessory": {
"type": "button",
"text": { "type": "plain_text", "text": "View PR" },
"url": "https://github.com/example/repo/pull/482"
}
}

Section with button accessory

Context

Displays small supplementary text, useful for metadata like timestamps, authors, or status indicators. Contains an array of text objects.

{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "Approved by *@alice*" },
{ "type": "plain_text", "text": "Updated 5 min ago" }
]
}

Divider

A simple horizontal rule to visually separate content. No additional fields.

{ "type": "divider" }

Actions

A row of up to 8 buttons. See Buttons below for the two button types (URL and interactive).

{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Approve" },
"action_id": "deploy_approve",
"value": "v2.4.1",
"style": "primary"
},
{
"type": "button",
"text": { "type": "plain_text", "text": "Reject" },
"action_id": "deploy_reject",
"value": "v2.4.1",
"style": "danger"
},
{
"type": "button",
"text": { "type": "plain_text", "text": "View Logs" },
"url": "https://monitoring.example.com/deploys/v2.4.1"
}
]
}

Actions block with buttons

Text Objects

Blocks use text objects to hold content. There are two types:

TypeFormattingUsed In
plain_textNo formatting, rendered as-isAll blocks and button labels
mrkdwnSlack-compatible markdown (*bold*, _italic_, `code`, ~strike~)section, context
{ "type": "plain_text", "text": "Simple text" }
{ "type": "mrkdwn", "text": "*Bold* and _italic_ with `code`" }

Note: header blocks and button text fields only accept plain_text.

Buttons

Buttons can appear in actions blocks or as a section accessory. Every button must have one of two behaviors:

URL Buttons

Include a url field. Clicking opens the URL in a new browser tab. No server-side callback is made.

{
"type": "button",
"text": { "type": "plain_text", "text": "Open Dashboard" },
"url": "https://example.com/dashboard"
}

Interactive Buttons

Include action_id and value fields (no url). When a user clicks the button, Roam sends a POST request to your app's Interactivity URL.

{
"type": "button",
"text": { "type": "plain_text", "text": "Approve" },
"action_id": "approve_request",
"value": "req-42",
"style": "primary"
}
note

Interactive buttons require your app to have an Interactivity URL configured. If you send a message with interactive buttons and no Interactivity URL is set, the API returns a 400 error.

Button Styles

StyleAppearance
(none)Default neutral style
"primary"Green, for confirmations and primary actions
"danger"Red, for destructive or critical actions

Button styles

Color

Roam supports a top-level color field on Block Kit messages. This renders as a colored vertical strip on the left side of the message — useful for indicating status at a glance.

This is a Roam extension. In Slack, color is an attachments feature and not available directly on Block Kit messages.

ValueColor
"good"Green
"warning"Yellow / amber
"danger"Red
"#RRGGBB"Any hex color (e.g. "#5B3FD9")
curl -X POST https://api.ro.am/v0/chat.post \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chat": "G-757dfe66-37b4-4772-baa5-8c86ec68c176",
"blocks": [
{
"type": "section",
"text": { "type": "mrkdwn", "text": "Build succeeded" }
}
],
"color": "good"
}'

Color variants

Interactivity

When a user clicks an interactive button, Roam sends a POST request to your app's Interactivity URL with details about the action.

Setup

  1. In Roam Administration > Developer, open your API client's settings.
  2. Under Interactivity URL, click Add Interactivity URL.
  3. Enter a publicly accessible HTTPS endpoint (e.g. https://example.com/roam/interactivity).
  4. Save the configuration.

The Interactivity URL section appears when your app has the chat:send_message scope.

Payload

When a button is clicked, Roam sends a POST request with a JSON body:

{
"type": "block_actions",
"clientId": "your-client-id",
"user": {
"id": "ad1e9cc0-0ffd-47e5-895c-2630a73327b4",
"email": "alice@example.com"
},
"message": {
"chatId": "C-295155ae-7df5-4ed5-9ebc-89a170559c81",
"timestamp": 1765602474760032,
"threadTimestamp": 1765602400000000
},
"blockId": "actions-1",
"actionId": "approve_request",
"value": "req-42"
}
FieldTypeDescription
typestringAlways "block_actions"
clientIdstringYour API client ID
userobjectThe user who clicked the button
user.idstringUser ID
user.emailstringVerified email address of the user
messageobjectThe message containing the button
message.chatIdstringChat where the button was clicked
message.timestampintegerTimestamp (Unix microseconds) of the message containing the button
message.threadTimestampintegerThread timestamp. Omitted if the message is not part of a thread.
blockIdstringID of the block containing the button
actionIdstringThe action_id from the button definition
valuestringThe value from the button definition

Request Signing

Interactivity requests are signed using the same Standard Webhooks mechanism as webhook deliveries. Your Webhook Signing Secret from the API Client settings is used to sign interactivity requests. See the webhook documentation for verification code examples.

Timeout and Error Handling

Your endpoint must respond within 3 seconds. If your action requires longer processing, acknowledge the request immediately and process asynchronously.

ScenarioWhat the user sees
2xx responseButton is marked as clicked
Response takes > 3 seconds"App didn't respond in time"
Connection error"App didn't respond"
Non-2xx response"App returned an error"

Limits

ConstraintLimit
Maximum blocks per message10
Maximum total payload size8,000 bytes
Maximum buttons per actions block8
Mutual exclusivityblocks cannot be combined with text or items

Complete Example

Here's a realistic deploy approval notification using multiple block types, both button types, and a color strip:

curl -X POST https://api.ro.am/v0/chat.post \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chat": "G-757dfe66-37b4-4772-baa5-8c86ec68c176",
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "Deploy Request: api-gateway v2.4.1" }
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Environment:* Production\n*Branch:* `main`\n*Commit:* `a1b2c3d` — Fix rate limiting edge case"
},
"accessory": {
"type": "button",
"text": { "type": "plain_text", "text": "View Diff" },
"url": "https://github.com/example/api-gateway/compare/v2.4.0...v2.4.1"
}
},
{ "type": "divider" },
{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "Requested by *@alice* · All CI checks passing" }
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Approve" },
"action_id": "deploy_approve",
"value": "api-gateway-v2.4.1",
"style": "primary"
},
{
"type": "button",
"text": { "type": "plain_text", "text": "Reject" },
"action_id": "deploy_reject",
"value": "api-gateway-v2.4.1",
"style": "danger"
}
]
}
],
"color": "#5B3FD9"
}'

Complete Block Kit example