This document provides design guidelines for developers looking to emit typed metadata events from their applications. These guidelines are intended to be strong recommendations (rather than prescriptive). Following these patterns will serve developers in providing better readability, maintainability, and DRYness across their workflows and applications.
While in the future this guide may serve as a launching point for additional features, none of these guidelines are enforced programmatically and only provide guidance in how to best use structured data.
This document repeatedly uses the word should (and occasionally MUST), as to be interpreted as described in [RFC2119].
Metadata Events are structured data payloads that contain information about events occurring in your Slack-connected application, in the form of a custom event_payload
as part of a message's metadata
property.
Developers can use different Slack API methods to transmit these events. One such method would be chat.postMessage
, where metadata
can be included as a JSON payload, and arrives in channel as part of a message (not visible to the user).
event_type
and an event_payload
. While order doesn't matter, developers should send event_type
first for readability.event_payload
, and property order within the event payload is not important.event_type
should be an alphanumeric string, and human readable. The value of this field may appear in the UI to developers, so keep this in mind when choosing a value. Developers should make an effort to name with the pattern <resource_name_singular>
and <action_in_past_tense>
{
"event_type": "job_created",
"event_payload": {
"job_id": 12345,
"date_created": 1623346792
}
}
A sample metadata
payload as part of chat.postMessage
.
When writing your JSON representation of event metadata:
[a-z]
, and may only contain characters, numbers [0-9]
and underscores [_]
{
"event_type": "example_created",
'event_payload': { ❌ // Always use double quotes
"user": 'U1234567', ❌ // Always use double quotes
"date_created": "1623346792" ❌ // Number (epoch) should not have quotes
"date_deleted": 1623346793 ✅
"9tails": "gotta catch em all!", ❌ // Don't begin properties with a number
"bad idea": true, ❌ // no spaces in property names
"the_quick_brown_fox_jumped_over_the_lazy_dog": false, ❌ // too long
"name": "Bologne" ✅
}
✅ Recommended | ❌ Not Recommended |
---|---|
user |
User |
user_id |
userId |
assigned_user_id |
assignedUserId |
a_user
, the_team
should NOT be used, rather user
, team
should be preferred.✅ Recommended | ❌ Not Recommended |
---|---|
poll_id |
pollId |
channel |
the_channel , theChannel , Channel |
"event_payload": {
"user": "U1234567", ✅
"date_created": 1623346792 ✅
"poll_id": 42 ✅
}
"event_payload": {
"User": "U1234567", ❌
"DateCreate": 1623346792 ❌
"the_poll_id": 42 ❌
array
. All other property names should be singular. (Including counts).user_objects
(just users
), or task_items
(just tasks
)Context | ✅ Recommended | ❌ Not Recommended |
---|---|---|
Property representing an array of User strings. | users |
user |
Property representing an array of tasks | tasks |
task_items |
Property representing an object with a number of properties. | profile |
profiles |
✅ Recommended
"event_payload":{
"job":{
"id":1001 ✅
},
"tasks":[
{
"id":420, ✅
"name":"Task"
},
{
"id":421,
"name":"Task 2"
}
],
❌ Not Recommended
"event_payload": {
"jobs":{ ❌
"id": 1001,
},
"task_items":[{ ❌
"id": 420,
"name":"Task"
},
{
"id": 421,
"name":"Task 2"
}],
Data should only be grouped as it is sensible semantically. Developers should not arbitrarily group data for visual convenience. We currently don't support objects within objects (unless a custom type has already been defined), or arrays that contain objects or other arrays.
❌ Not Recommended
"event_payload": {
"user":{ ✅
"id": 123
"date_created": 1623857773,
"names": { ❌ /* There is no reason to group these unless the object has a use */
"first_name": "John",
"middle_name": "Dino",
"last_name": "Hammond",
},
}
}
✅ Recommended
"event_payload": {
"user":{ ✅
"id": 123,
"date_created": 1623857773,
"first_name": "John",
"middle_name": "Dino",
"last_name": "Hammond"
}
}
"event_payload": {
"job_id": 12345,
"date_created": 1623346792
"poll_name": "my_poll",
"poll_id": 42
"poll_vote_count": 19
"permalink_url": "https://example.com/my_poll/42"
}
"event_payload": {
"job_id": 12345,
"poll": {
"date_created": 1623346792
"name": "my_poll",
"id": 42
"vote_count": 19
"permalink_url": "https://example.com/my_poll/42"
}
}
Using structured hierarchy where meaningful provides a number of benefits:
date_created
).Fields that are required should always have a value. Wherever possible, avoid sending null
.
Optional fields that are not sent as part of an event should be assumed to be null
.
Consider configuring your event consumption in such a way that the setting and unsetting of specific properties is controlled by their presence in the event payload.
For example, if we wanted to set the first and last name, our event would look like so:
✅ Recommended
"event_payload": {
"first_name": "John",
"last_name": "Hammond"
}
If the user already existed, but had been previously set up with a middle name, and we wanted to unset the middle name, our event would look like so (the same):
✅ Recommended
"event_payload": {
"first_name": "John",
"last_name": "Hammond"
}
Anti-patterns:
❌ Not Recommended
"event_payload": {
"first_name": null, ❌ // never set required values to be null
"last_name": "Hammond"
}
"event_payload": {
"first_name": "John",
"middle_name": null, ❌ // not recommended way to unset optional value
"last_name": "Hammond"
}
While most basic values can be defined using primitives, consider using custom types and objects wherever it may improve clarity, extensibility, and reusability.
Examples:
mybusiness#/types/user_id
as opposed to using a string
or integer
.mybusiness#/types/user
object will let you add new properties to the type object later without changing the event schemaslack#/types/channel
slack#/types/channel_id
slack#/types/user
slack#/types/user_id
slack#/types/team
slack#/types/team_id
slack#/types/timestamp
Properties
date_{past_tense_verb}
like date_created
, date_archived
. The exception to this rule is when the value of the date going into the property is intended to generally be greater than the timestamp of the event.✅ Recommended | ❌ Not Recommended |
---|---|
date_expired |
date_expire |
date_created |
date_create , created_at |
date
and appending at
✅ Recommended | ❌ Not Recommended |
---|---|
expires_at |
date_expires |
send_at |
date_send |
ticket_due_at |
date_ticket_due , ticket_due |
date_{resource}_{past_tense_verb}
✅ Recommended | ❌ Not Recommended |
---|---|
date_user_created |
user_creation_date |
date_task_deleted |
task_date_deleted |
✅ Recommended
"event_payload": {
"date_project_created":1515472552, ✅
"task":{
"id": 1002,
"date_created":1525472552, ✅
"due_at": 1725472552 ✅
},
❌ Not Recommended
"event_payload": {
"date_project_created":1515472552, ✅
"task":{
"id": 1002,
"date_task_created": 1525472552, ❌
},
Values
✅ Recommended | ❌ Not Recommended |
---|---|
1525472552 |
Fri, 04 May 2018 22:22:31 GMT |
1514764800 |
2018-01-01T00:00:00Z |
types:
date_created:
description: Date object was created
title: Date Created
type: slack#/types/timestamps
example: 1525472552
Properties
since
, until
- inclusivebefore
, after
- exclusive✅ Recommended | ❌ Not Recommended |
---|---|
since_completed |
since_complete |
until_completed |
until_complete |
before_expired |
time_to_expire |
after_expired |
time_after_expired |
Values
since_deleted:
description: How long its been since the issue was deleted
title: Time Since Deleted
type: integer
example: 3600
Properties
date_<noun>_start
**and date_<noun>_end
.date_start
and then since_started
to describe objects which have a start and an end (interval).Values
Properties
<singular_resource>_count
✅ Recommended | ❌ Not Recommended |
---|---|
response_count |
num_responses |
ticket_count |
num_tickets |
authed_member_count |
members_authed |
item_count |
total_items |
user_count |
users |
✅ Recommended
"event_payload": {
"user_count": 10,
❌ Not Recommended
"event_payload": {
"users": 10,
Values
issue_count:
description: How many issues are there
title: Issue Count
type: integer
example: 12
Properties
is_
or has_
✅ Recommended | ❌ Not Recommended |
---|---|
is_expired |
expired |
is_due |
due |
has_attachments |
attachments |
Values
true
or false
.Properties
Context | ✅ Recommended | ❌ Not Recommended |
---|---|---|
Property representing an incident level | severity |
severities |
Values
severity:
description: Severity of the incoming incident
title: Incident Severity
type: enum:["SEV1", "SEV2", "SEV3"],
example: "SEV2"
"metadata":{
"event_type": "new_incident",
"event_payload": {
"severity": "SEV2"
_id
"event_payload": {
"user_id": "U1234M112", ✅
}
"event_payload": {
"user": "U1234M112" ❌
}
Properties
slack#/types
✅ Recommended
types:
assignee_user_id:
description: Slack user ID of assignee user
title: Assignee user's ID
type: slack#/types/user_id ✅
example: U01234XM1
❌ Not Recommended
types:
assignee_user_id:
description: Slack user ID of assignee user
title: Assignee user's ID
type: string ❌ // Use the slack user_id type
example: U01234XM1
Properties
message_timestamp
✅ Recommended | ❌ Not Recommended |
---|---|
message_time stamp |
timestamp , ts |
Values
✅ Recommended | ❌ Not Recommended |
---|---|
"1525471335.000320" |
1525471335000320 |
"1525471335.000320" |
1525471335 |
✅ Recommended
"metadata":{
"event_type": "slack_message_processed",
"event_payload": {
"date_received": 1525472552, ✅
"message_timestamp": "1525471335.000320" ✅
❌ Not Recommended
"metadata":{
"event_type": "slack_message_processed",
"event_payload": {
"date_received": "2018-01-01T00:00:00Z", ❌
"message_timestamp": 1525471335 ❌
±DD.DDDD±DDD.DDDD
degrees format.{
"slack_hq": "+40.6894-074.0447"
}
Here are some example patterns of types and events as they would be described by an app manifest file, and the resulting output of your events:
This is what an example message schema might look like if you were describing an event sent by a product like PagerDuty or similar.
Types - Manifest Schema
types:
incident_severity:
type: string
enum: ["S1", "S2", "S3", "S4"]
choices:
S1:
title: Severity 1
S2:
title: Severity 2
S3:
title: Severity 3
S4:
title: Severity 4
incident:
title: Incident
description: Represents an incident that was opened and hopefully resolved
type: object
required:
- id
- title
- summary
- severity
- reported_by
- created_at
properties:
id:
type: integer
title: Incident ID
title:
type: string
summary:
type: string
severity:
type: "#/types/incident_severity"
reported_by:
type: slack#/types/user_id
assigned_to:
type: slack#/types/user_id
description: Who was on call and assigned to the incident
nullable: true
date_created:
type: slack#/types/timestamp
date_closed:
type: slack#/types/timestamp
nullable: true
Event - Manifest Schema
events:
incident_created:
title: Incident Created
description: Event triggered when a new incident is created
type: object
required:
- incident
properties:
incident:
type: "#/types/incident"
Metadata - Message Object
"metadata": {
"event_type": "incident_created",
"event_payload": {
"incident": {
"id": 9001,
"title": "A new incident was created!",
"summary": "Something really bad!",
"severity": "S1",
"reported_by": "U12345678",
"date_created": 1623792755
}
}