Update Slack Messages After Button Clicks Using response_url
Handle Slack interactive button clicks and update the original message with Block Kit by posting to response_url.
13 Aug 2025, 07:49 UTC

A button click should change the message, not start a new thread
You send a Slack message with a Block Kit button—perhaps "Approve request" or "Mark complete." A user clicks it, and your app receives an action payload. But the message in the channel still shows the button as if nothing happened. Without an update, users click again, or you flood the channel with follow-up messages.
The useful takeaway: Slack includes a response_url in the action payload. Your app can POST a new Block Kit payload to that URL with replace_original set to true, which updates the message in place instead of appending another one.
Send a message with an interactive button
Block Kit is Slack's UI framework for messages, modals, and home tabs. To send a button, call chat.postMessage with a blocks array. Each button needs an action_id, which is the stable contract your app uses to identify what was clicked.
{
"channel": "C12345678",
"text": "Expense request pending",
"blocks": [
{
"type": "section",
"text": {
"type": "plain_text",
"text": "Expense request for $120.00 needs approval."
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Approve"
},
"action_id": "approve_expense",
"value": "expense_98765"
}
]
}
]
}
Send this from your app server with a valid bot token. The chat:write OAuth scope is required. Replace C12345678 with your channel ID and expense_98765 with your internal record identifier.
Verify and handle the action payload
When a user clicks the button, Slack sends an HTTP POST to your app's Request URL. The body contains type, user, channel, message, action_ts, and response_url. Before trusting it, verify the request using your signing secret with the X-Slack-Signature and X-Slack-Request-Timestamp headers. Bolt SDKs implement this verification; raw middleware needs an HMAC check.
Persist any state you need—such as the expense ID—keyed by action_ts, channel, and user. Slack does not carry arbitrary app session data in the payload.
Update the original message through response_url
After validation, POST a replacement payload to response_url. Set replace_original to true so Slack updates the message where the button appeared.
{
"replace_original": true,
"blocks": [
{
"type": "section",
"text": {
"type": "plain_text",
"text": "✅ Expense request approved."
}
}
]
}
Run this POST from your app server or terminal with Content-Type: application/json. Required: outbound HTTPS to Slack and a valid response_url. Replace the URL and block content with your own values. Expected check: HTTP 200 and the original message updates in Slack. Risk: response_url values expire and may have reuse limits, so do not log them or rely on them for delayed jobs.
An ephemeral reply is visible only to the person who clicked and does not change the message for everyone. Use replace_original when the outcome should be visible to the channel.
Trade-offs, limits, and verification
response_urlvalues are time-limited and may have reuse limits. Do not depend on them for long-running background jobs without persisting state.- Slack enforces rate limits and OAuth scopes. Missing
chat:writeorinteractive_componentscauses API errors. - Block Kit limits vary by surface and change over time. Validate against current Slack API documentation before deploying.
Because posting to response_url changes state in Slack, keep the response_url and action_ts in a log so you can send a follow-up message if the replacement fails.
To verify: install the app with interactivity enabled and a public HTTPS Request URL, post the button message, click it, inspect your Request URL logs for the payload and response_url, then POST the replacement JSON. Open the Slack channel and confirm the original message now shows the updated text and no button. To test signature verification, replay a captured payload with an altered timestamp; your service should reject it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.