Common pattern — live status boards: Post a status message once with message/send (capturing messageTs), then update it repeatedly with message/update as the underlying state changes. This gives channel members a single, always-current message to reference rather than a stream of repetitive updates.
When to Use
- Live status boards: A pinned message in
#incidents tracks the current status of a service outage. As the incident progresses through "Investigating" → "Identified" → "Monitoring" → "Resolved", the workflow updates the same message in-place rather than flooding the channel with new posts.
- Ticket tracker integration: When a Jira issue transitions status (e.g. from "In Progress" to "In Review"), the workflow updates the associated Slack message card to reflect the new state with a fresh timestamp.
- Rolling leaderboard: A weekly sales leaderboard is posted on Monday and updated every few hours as new deals close, keeping the top-of-channel message current without creating a new post each time.
- Approval status update: After an approval decision is made via
message/sendAndWait, the original approval request card is updated from "Pending Approval" to "Approved ✓" or "Rejected ✗" so the channel history is accurate.
- Build/deploy status: A CI/CD pipeline updates a Slack message through "Queued" → "Building" → "Testing" → "Deployed to staging" → "Deployed to production" as each pipeline stage completes.
Configuration
Connection
| Field | Required | Description |
botToken | Required | Bot Token in xoxb-... format. Must have chat:write scope and must be the same token used to originally post the message. Store in BizFirst Credentials Manager. |
Operation
| Field | Required | Description |
channel | Required | Channel ID containing the message to update. Must match the channel the message was originally posted to. |
messageTs | Required | Timestamp of the message to update (e.g. 1716700412.087200). This is the messageTs returned by the original message/send node. Treat it as the message's unique ID. |
text | Optional | New text content to replace the existing message text. Required if blocksJson is not provided. |
blocksJson | Optional | New Block Kit blocks JSON array string. When provided, completely replaces the existing blocks. Required if text is not provided. Pass an empty array [] to remove all blocks and fall back to plain text. |
At least one of text or blocksJson is required. Providing neither will result in a VAL_MISSING_CONTENT validation error. If you provide both, blocksJson is rendered and text serves as the fallback/notification text.
Sample Configuration
{
"resource": "message",
"operation": "update",
"botToken": "{{ $credentials.slackBotToken }}",
"channel": "C04INCIDENTS",
"messageTs": "{{ $output.postIncident.messageTs }}",
"text": "RESOLVED: payments-api incident — Service fully restored at {{ $now }}",
"blocksJson": "[{\"type\":\"header\",\"text\":{\"type\":\"plain_text\",\"text\":\":white_check_mark: Incident Resolved\"}},{\"type\":\"section\",\"fields\":[{\"type\":\"mrkdwn\",\"text\":\"*Service:*\\n`payments-api`\"},{\"type\":\"mrkdwn\",\"text\":\"*Status:*\\n:large_green_circle: Fully Restored\"},{\"type\":\"mrkdwn\",\"text\":\"*Resolved at:*\\n{{ $now }}\"},{\"type\":\"mrkdwn\",\"text\":\"*Duration:*\\n{{ $input.incidentDurationMinutes }} minutes\"}]}]"
}
Validation Errors
| Error Code | Cause |
message_not_found | No message exists with the given messageTs in the specified channel. The message may have been deleted, or the timestamp is incorrect. |
cant_update_message | The bot token is not the original sender of the message, or the message type cannot be edited (e.g. system messages, bot message subtypes with restrictions). |
channel_not_found | The specified channel does not exist or is not accessible to the bot token. |
edit_window_closed | Workspace admin settings restrict message editing after a certain time window. The original message is too old to edit. |
invalid_blocks | The blocksJson string is malformed or references unsupported block types. |
VAL_MISSING_CHANNEL | The channel field is empty or not provided. |
VAL_MISSING_TS | The messageTs field is empty or not provided. |
VAL_MISSING_CONTENT | Neither text nor blocksJson was provided. At least one is required. |
Output
Success Port
Fires after Slack confirms the message has been updated. The messageTs is unchanged — the timestamp uniquely identifies the message and does not change on edit.
| Field | Type | Description |
channel | string | Channel ID where the message was updated. |
messageTs | string | Timestamp of the updated message (same as the original — it does not change on edit). |
status | string | "ok" on success. |
errorCode | string | Empty on success. |
payload | object | Full raw Slack API response including the updated message object. |
Error Port
Fires when the message cannot be found, the bot is not the original sender, the edit window has closed, or the API call fails.
| Field | Type | Description |
status | string | "error" |
errorCode | string | Slack error code, e.g. message_not_found, cant_update_message. |
payload | object | Full raw Slack error response. |
Sample Output
{
"channel": "C04INCIDENTS",
"messageTs": "1716700412.087200",
"status": "ok",
"errorCode": "",
"payload": {
"ok": true,
"channel": "C04INCIDENTS",
"ts": "1716700412.087200",
"message": {
"type": "message",
"subtype": "bot_message",
"text": "RESOLVED: payments-api incident — Service fully restored.",
"ts": "1716700412.087200",
"edited": {
"user": "U0LAN0Z89",
"ts": "1716710000.000000"
}
}
}
}
Expression Reference
| Field | Expression | Notes |
| Updated channel | {{ $output.updateStatus.channel }} | Resolved channel ID — confirm the update landed in the expected channel. |
| Message timestamp | {{ $output.updateStatus.messageTs }} | Same as the original ts. Use this for further updates or to fetch the permalink. |
| Operation status | {{ $output.updateStatus.status }} | "ok" on success. Check this in downstream IfCondition nodes. |
| Error code | {{ $output.updateStatus.errorCode }} | Non-empty if the update failed — log for monitoring. |
| Edit timestamp | {{ $output.updateStatus.payload.message.edited.ts }} | The Slack-assigned timestamp of when the edit occurred — useful for audit records. |
Node Policies & GuardRails
| Policy Area | Recommendation |
| Store the original messageTs | Always persist the messageTs returned by message/send in a workflow variable or database record. Without it, you cannot update or delete the message later. Use a VariableAssignment node immediately after the send. |
| Same token requirement | The bot token used for message/update must be the same token that sent the original message. Using a different token or a user token will result in cant_update_message. |
| Edit window policy | Some Slack workspace administrators enable a message edit window (e.g. only editable within 5 minutes). Design workflows that need to update messages to do so promptly after the original send. Check workspace settings if you encounter edit_window_closed errors. |
| Block replacement is complete | When providing blocksJson, the new array completely replaces all existing blocks. You must re-provide all blocks you wish to keep, not just the changed ones. Build the full updated Block Kit payload in your workflow before calling this node. |
| Error port handling | Connect the error port — especially in incident management workflows where failing to update a status message could mislead the team about the current state. |
| Credential storage | Store the Bot Token in BizFirst Credentials Manager. The same credential used for the original send should be referenced here. |
Examples
Incident Status Update — "Resolved"
{
"resource": "message",
"operation": "update",
"botToken": "{{ $credentials.slackBotToken }}",
"channel": "C04INCIDENTS",
"messageTs": "{{ $vars.incidentMessageTs }}",
"text": "RESOLVED: payments-api — fully restored",
"blocksJson": "[{\"type\":\"header\",\"text\":{\"type\":\"plain_text\",\"text\":\":white_check_mark: Incident Resolved\"}},{\"type\":\"section\",\"fields\":[{\"type\":\"mrkdwn\",\"text\":\"*Service:* `{{ $vars.serviceName }}`\"},{\"type\":\"mrkdwn\",\"text\":\"*Resolved at:* {{ $now }}\"}]}]"
}
Updates an incident card originally posted during an alert. $vars.incidentMessageTs was stored by a VariableAssignment node at incident creation time. The resolved card replaces the original alert card in-place in #incidents.
CI/CD Build Progress Tracker
{
"resource": "message",
"operation": "update",
"botToken": "{{ $credentials.slackBotToken }}",
"channel": "C04DEPLOYS01",
"messageTs": "{{ $vars.buildMessageTs }}",
"text": "Build #{{ $input.buildNumber }} — {{ $input.currentStage }}",
"blocksJson": "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"*Build #{{ $input.buildNumber }}* — {{ $input.currentStage }}\\nCommit: `{{ $input.commitSha }}`\\nBranch: `{{ $input.branch }}`\"}},{\"type\":\"section\",\"fields\":[{\"type\":\"mrkdwn\",\"text\":\"*Test:* {{ $input.testStatus }}\"},{\"type\":\"mrkdwn\",\"text\":\"*Deploy:* {{ $input.deployStatus }}\"}]}]"
}
A CI/CD workflow posts one message per build using message/send and updates it at each stage transition using this node. Channel members see a single build card per deployment rather than a series of status messages.
Approval Card — Decision Recorded
{
"resource": "message",
"operation": "update",
"botToken": "{{ $credentials.slackBotToken }}",
"channel": "{{ $output.waitApproval.channel }}",
"messageTs": "{{ $output.waitApproval.messageTs }}",
"text": "PO #{{ $input.poNumber }} — {{ $output.waitApproval.replyText | upper }} by {{ $output.lookupApprover.displayName }}",
"blocksJson": "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"*PO #{{ $input.poNumber }}* — :white_check_mark: *{{ $output.waitApproval.replyText | upper }}*\\nDecision by: {{ $output.lookupApprover.displayName }}\\nAt: {{ $now }}\"}}]"
}
After the message/sendAndWait node receives an approval decision, this update node edits the original approval request card to show the final decision. The card is updated from "Pending Approval" to the actual decision, providing a permanent record in the channel history.