Using message metadata
Messages are how people communicate with people. Message metadata is how apps communicate with apps and how apps communicate with Slack.
By attaching structured metadata to messages, your app enables Slack to surface rich experiences in the Activity feed, power agent-to-human workflows, and allow other apps and agents listening on the Events API to respond intelligently.
Defining metadata
Metadata uses a JSON structure attached to messages via the metadata parameter. Each metadata payload contains:
event_type: a namespaced string identifying the type of eventevent_payload: a JSON object with the event-specific fields
App manifest configuration
Your app must register metadata schemas in its app manifest before sending metadata. Invalid metadata returns a warning and is ignored. The following is an example of how that might look.
{
"features": {
"bot_user": {
"display_name": "My Agent",
"always_online": true
}
},
"settings": {
"event_subscriptions": {
"bot_events": ["message.im", "message.groups"]
}
},
"metadata": {
"event_subscriptions": [
{
"event_type": "notification",
"schema": {}
},
{
"event_type": "approval",
"schema": {}
}
]
}
}
Standard metadata schemas
Slack defines standard metadata schemas for common interaction patterns. When your app uses these schemas, Slack can automatically surface the message in the Activity feed with native UI affordances (approve/deny buttons, notification badges, etc.) and other agents can respond without custom parsing.
Notification schema
Event type: notification
Use notification when your app or agent needs to alert a user about something that requires their attention but does not require a decision.
Schema
{
"metadata": {
"event_type": "notification",
"event_payload": {
"notification_type": "info",
"title": "Deployment completed",
"source": {
"ref_id": "deploy-7842",
"ref_url": "https://example.com/deploys/7842"
},
"urgency": "normal",
"category": "status_update",
"expires_at": 1716500000
}
}
}
Properties
| Field | Type | Description | Required? |
|---|---|---|---|
notification_type | Enum | One of: info, warning, error, success | Required |
title | String | Short summary displayed in the Activity feed; max 150 characters | Optional |
source.ref_id | String | External reference ID for correlation | Optional |
source.ref_url | String | Deep link back to the source system | Optional |
urgency | Enum | One of: low, normal, high, critical | Optional |
category | Enum | One of: status_update, alert, reminder, task_complete, agent_update | Optional |
expires_at | Integer | Unix timestamp after which the notification is no longer relevant | Optional |
context | Object | Freeform key-value pairs for additional context | Optional |
Schema reference:
{
"event_type": "notification",
"event_payload": {
"notification_type": "info | warning | error | success",
"title": "string (max 150)",
"source": {
"ref_id": "string",
"ref_url": "string (URL)"
},
"urgency": "low | normal | high | critical",
"category": "status_update | alert | reminder | task_complete | agent_update",
"expires_at": "integer (unix timestamp)",
"context": {}
}
}
Example: agent status update
{
"metadata": {
"event_type": "notification",
"event_payload": {
"notification_type": "success",
"title": "Research task completed",
"source": {
"ref_id": "task-991",
"ref_url": "https://agent.example.com/tasks/991"
},
"urgency": "normal",
"category": "agent_update",
"context": {
"agent_name": "Research Agent",
"duration_seconds": 142,
"output_type": "summary"
}
}
}
}
Example: error alert
{
"metadata": {
"event_type": "notification",
"event_payload": {
"notification_type": "error",
"title": "Pipeline failed: data-ingest-prod",
"source": {
"ref_id": "run-44812",
"ref_url": "https://ci.example.com/runs/44812"
},
"urgency": "high",
"category": "alert"
}
}
}
Approval schema
Event type: slack_approval
Use approval when your app or agent needs a human decision before proceeding. Slack surfaces approval metadata in the Activity feed with native approve/deny buttons, enabling users to take action without navigating to the message.
Schema
{
"metadata": {
"event_type": "approval",
"event_payload": {
"approval_type": "action",
"title": "Approve deployment to production",
"source": {
"ref_id": "deploy-7842",
"ref_url": "https://example.com/deploys/7842"
},
"urgency": "high",
"approvers": ["U0123456789"],
"expires_at": 1716500000,
"options": {
"approve_label": "Approve",
"deny_label": "Deny"
}
}
}
}
Properties
| Field | Type | Description | Required? |
|---|---|---|---|
approval_type | Enum | One of: action, access, resource, expense, tool_use | Required |
title | String | What is being approved; max 150 characters | Required |
source.ref_id | String | External reference ID for correlation | Optional |
source.ref_url | String | Deep link to the approval in the source system | Optional |
urgency | Enum | One of: low, normal, high, critical | Optional |
approvers | Array [string] | List of Slack user IDs authorized to approve. If empty, anyone in the conversation can approve | Optional |
expires_at | Integer | Unix timestamp after which the approval is auto-expired | Optional |
options.approve_label | String | Custom label for the approve button (default: "Approve") | Optional |
options.deny_label | String | Custom label for the deny button (default: "Deny") | Optional |
context | Object | Freeform key-value pairs for additional context | Optional |
Schema reference:
{
"event_type": "approval",
"event_payload": {
"approval_type": "action | access | resource | expense | tool_use",
"title": "string (max 150)",
"source": {
"ref_id": "string",
"ref_url": "string (URL)"
},
"urgency": "low | normal | high | critical",
"approvers": ["string (user IDs)"],
"expires_at": "integer (unix timestamp)",
"options": {
"approve_label": "string (max 24)",
"deny_label": "string (max 24)"
},
"context": {}
}
}
Example: agent requesting permission
{
"metadata": {
"event_type": "approval",
"event_payload": {
"approval_type": "action",
"title": "Agent wants to send email to 12 candidates",
"source": {
"ref_id": "action-5523",
"ref_url": "https://agent.example.com/actions/5523"
},
"urgency": "normal",
"approvers": ["U9876543210"],
"context": {
"agent_name": "Recruiting Agent",
"action_description": "Send personalized outreach emails",
"recipient_count": 12,
"template_id": "outreach-v2"
}
}
}
}
Example: tool access request (admin)
{
"metadata": {
"event_type": "approval",
"event_payload": {
"approval_type": "tool_use",
"title": "Agent requesting access to Salesforce API",
"source": {
"ref_id": "tool-req-881",
"ref_url": "https://admin.example.com/requests/881"
},
"urgency": "high",
"approvers": ["U1111111111", "U2222222222"],
"expires_at": 1716586400,
"context": {
"agent_name": "Sales Assistant",
"tool_name": "salesforce_api",
"scope": "read_contacts",
"justification": "Need to look up account details for meeting prep"
}
}
}
}
Example: expense approval
{
"metadata": {
"event_type": "approval",
"event_payload": {
"approval_type": "expense",
"title": "Approve $2,400 software license renewal",
"source": {
"ref_id": "exp-20948",
"ref_url": "https://concur.example.com/expenses/20948"
},
"urgency": "normal",
"approvers": ["U3333333333"],
"context": {
"vendor": "Figma",
"amount": "2400.00",
"currency": "USD",
"requester_name": "Jamie Chen",
"cost_center": "Engineering"
}
}
}
}
Custom metadata schemas
Beyond the standard schemas, you can define your own event_type for app-specific use cases. Custom event types should be namespaced to your app:
{
"metadata": {
"event_type": "my_app.ticket_created",
"event_payload": {
"ticket_id": "PROJ-1234",
"priority": "high",
"assignee": "U0123456789"
}
}
}
Register custom schemas in your app manifest under metadata.event_subscriptions. Read more about designing your custom schema in Designing metadata event schema.
Sending metadata
Attach metadata to any message sent via the chat.postMessage API method:
POST https://slack.com/api/chat.postMessage
Content-Type: application/json; charset=utf-8
Authorization: Bearer xoxb-your-token
{
"channel": "C0123456789",
"text": "Your deployment to production is ready for approval.",
"metadata": {
"event_type": "approval",
"event_payload": {
"approval_type": "action",
"title": "Approve deployment to production",
"source": {
"ref_id": "deploy-7842"
},
"urgency": "high",
"approvers": ["U0123456789"]
}
}
}
Example: Combining metadata with action blocks
You can attach metadata when posting messages with Block Kit. The message text and blocks are what users see in the conversation; the metadata is what Slack and other apps use to enable richer experiences. For approvals, pair the metadata with a single actions block containing approve/deny buttons. Slack will surface these buttons directly in the Activity feed:
{
"channel": "C0123456789",
"text": "Approve deployment to production?",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Deploy to production*\nBranch: `main` | Commit: `a1b2c3d`"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Approve" },
"style": "primary",
"action_id": "approve_deploy",
"value": "deploy-7842"
},
{
"type": "button",
"text": { "type": "plain_text", "text": "Deny" },
"style": "danger",
"action_id": "deny_deploy",
"value": "deploy-7842"
}
]
}
],
"metadata": {
"event_type": "approval",
"event_payload": {
"approval_type": "action",
"title": "Approve deployment to production",
"source": {
"ref_id": "deploy-7842"
},
"urgency": "high"
}
}
}
When a message has a single actions block and approval metadata, the buttons are surfaced directly in the Activity feed. Users can approve or deny without navigating to the channel.
Example: Task created
This example shows a message sent when a new task is created. It include metadata about the task for project management tools to pull in:
POST /api/chat.postMessage
Host: slack.com
Authorization: Bearer xoxb-6050345600-60510457-…
Content-type: application/json; charset=utf-8
{
"channel": "C123ABC456",
"text": "New task Added by @sam - Redesign homepage",
"metadata": {
"event_type": "task_created",
"event_payload": {
"id": "TK-2132",
"summary": "New issue with the display of mobile element",
"description": "An end user has found a problem with the new mobile container for data entry. It was reproduced in the current version of IOS.",
"priority": "HIGH",
"resource_type": "TASK"
}
}
}
Example: New member added
This example shows a message that is part of an onboarding app. The message is sent when a new person is added to a team channel or user group within Slack. This app sends along a new_teammate event with corresponding metadata:
POST /api/chat.postMessage
Host: slack.com
Authorization: Bearer xoxb-6050345600-60510457-…
Content-type: application/json; charset=utf-8
{
"channel": "C123ABC456",
"text": "New teammate @Billy just joined",
"metadata": {
"event_type": "new_teammate",
"event_payload": {
"id": "TK-2132",
"summary": "New teammate has been added to the channel",
"description": "@Billy is a new teammate and needs to be added to the necessary channels",
"priority": "HIGH",
"resource_ type": "TASK"
}
}
}
You can then have a Slack app that performs other actions, such as:
- Adds @Billy to other team channels
- Sends a direct message containing the
descriptionfield contents - Sends a direct message to people in channel reminding them to post a welcome message
- Send an ephemeral message to @Billy with suggested channels to join
Example: External feedback received via webhook
This example shows how you can use message metadata from an external source using webhooks. Your app can listen for posts from a website where users can post feedback. When a post does occur, your app posts a message in #feedback-channel with metadata attached:
POST /api/chat.postMessage
Host: slack.com
Authorization: Bearer xoxb-6050345600-60510457-…
Content-type: application/json; charset=utf-8
{
"channel": "C123ABC456",
"text": "New feedback received",
"metadata": {
"event_type": "feedback_received",
"event_payload": {
"id": "TK-2132",
"summary": "User submitted feedback",
"description": "Someone submitted feedback on this page",
"feedback_message":"This page is awesome, thanks for writing it!",
"page_url": "http://docs.slack.dev",
"priority": "LOW",
"page_owner": "@haile",
"resource_ type": "TASK"
}
}
}
You can then have a Slack app that performs other actions, such as:
- Reacts to the feedback submissions
- Assigns the task to a user in the channel.
- Sends direct messages to the
page_ownerfound in theevent_payload
The number of actions being triggered is up to you and your app's utilization of message metadata.
Receiving metadata
Your app may receive metadata with either the Web API or the Events API.
Web API
Use the conversations.history API method with the parameter and value include_all_metadata=true:
GET https://slack.com/api/conversations.history?channel=C0123456789&include_all_metadata=true
The response will include metadata within the message object:
{
"ok": true,
"messages": [
{
"type": "message",
"user": "U123ABC456",
"text": "New task Added by @sam - Redesign homepage",
"app_id": "A01234",
"metadata": {
"event_type": "task_added",
"event_payload": {
"id": "11223",
"title": "Redesign homepage",
"creator": "sam@acme-corp.com",
"created_at": "1610561787",
"priority": "high",
"status": "triage"
}
},
"ts": "1512085950.000216"
},
{
"type": "message",
"user": "U123ABC456",
"text": "I'm going to start working on :point_up: task",
"ts": "1512104434.000490"
}
],
"has_more": true,
"pin_count": 0,
"response_metadata": {
"next_cursor": "bmV4dF90czoxNTEyMDg1ODYxMDAwNTQz"
}
}
Events API
Subscribe to metadata events via the Events API to react when apps post notifications or approvals.
-
Configure your Slack app to request the
message_metadata:readscope either through the app settings or by adding it directly to the app manifest. -
Subscribe to the following events, as needed:
Event Description message_metadata_deletedMessage metadata was deleted message_metadata_postedMessage metadata was posted message_metadata_updatedMessage metadata was updated -
Within your app's manifest file under
settings, add in yourmetadata_subscriptionsunderevent_subscriptions. Each entry requires anapp_idandevent_typepair where either (but not both) can be a wildcard'*'. Do not use the wildcard unless absolutely necessary. Here is an example snippet of the what the app manifest file might look like:"settings": {"event_subscriptions": {"request_url": "https://example.ngrok.io/slack/events","bot_events": ["message_metadata_posted"],"metadata_subscriptions": [{"app_id": "A098765","event_type": "your-event-name"},{"app_id": "A0123456","event_type": "your-event-name"}]}}Filter by
event_typeto listen for specific schemas:{"metadata_subscriptions": [{"app_id": "A0123456789","event_type": "notification"},{"app_id": "A0123456789","event_type": "approval"}]}Listening for any app's notifications or approvals
To build an agent that responds to all notifications or approvals in a workspace (not just from your own app), subscribe to the standard schemas:
{"metadata_subscriptions": [{"event_type": "notification"},{"event_type": "approval"}]}This enables use cases like:
- A triage agent that routes critical notifications to on call
- A dashboard that aggregates pending approvals across all apps
- An assistant that reminds users of expiring approvals
Best practices
- Always attach metadata to agent messages. Every message from an agent or automated app should carry metadata. Even if the UI does not yet render a specific field, other agents and future Slack features will use it.
- Use standard schemas when they fit. If your message is a notification or requires approval, use
notificationorapproval. This ensures Slack surfaces the message in the Activity feed with the best possible UX. - Keep titles short and actionable. Titles appear in the Activity feed and push notifications. Write them as a human would scan them: "Approve PTO request" not "A PTO request has been submitted and is awaiting your approval."
- Set appropriate urgency. Reserve
criticalfor situations that genuinely require immediate attention. Overuse of high urgency trains users to ignore it. - Include
source.ref_idfor correlation. This allows agents and automations to match metadata back to the originating system without parsing message text. - Set
expires_aton time-sensitive items. Approvals and notifications that are no longer relevant should expire gracefully rather than sitting in the Activity feed indefinitely. - Scope approvers intentionally. If only specific people should approve, list them in
approvers. If anyone in the channel can approve, omit the field. - Pair approvals with a single actions block. One
actionsblock with approve/deny buttons + approval metadata = the approval surfaces natively in the Activity feed.