Slack Error Guide: 'message_not_found' — Fix Bad Timestamps
Fix the Slack message_not_found error: pass the exact ts as a string with its matching channel to chat.update and chat.delete, and handle deleted messages.
- #slack
- #api
- #troubleshooting
- #errors
Stuck on this Slack error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
The message_not_found error means Slack could not locate a message with the ts (timestamp) you supplied in the channel you supplied. It appears on chat.update, chat.delete, reactions.add, reactions.remove, pins.add, and chat.getPermalink. In Slack, a message is uniquely identified by the pair (channel, ts) — the ts alone is not global. The most common cause is a mismatched or reformatted ts: truncating it, rounding it, or pairing it with the wrong channel id. Editing a live status message or deleting a completed deploy notice is where this usually bites.
Slack returns HTTP 200 with ok:false:
{
"ok": false,
"error": "message_not_found"
}
It occurs whenever (channel, ts) does not point at an existing message the token can act on.
Symptoms
chat.update/chat.deletefails withmessage_not_foundright after a successfulchat.postMessage.- Reactions fail on a message that clearly exists in the UI.
- A
tscopied from a URL or logged as a float has lost precision. - Editing a threaded reply using the parent’s channel but a reply’s
ts, or vice versa. - The message was already deleted by a user or another process.
Common Root Causes
1. Wrong or reformatted ts
The ts is a string like 1720526400.001900. Storing it as a float, rounding it, or dropping trailing zeros changes the value so it no longer matches.
2. Mismatched channel
You must pass the SAME channel id the message was posted to. Using a DM id, a different channel, or the user id instead of the channel breaks the pair.
3. The message was deleted
A user or another automation already removed the message; there is nothing to update.
4. Using thread_ts instead of the message ts (or vice versa)
Acting on a reply requires that reply’s own ts, not the parent thread_ts.
5. Ephemeral messages
Messages posted with chat.postEphemeral have no permanent ts you can update or delete.
Diagnostic Workflow
Step 1: Capture the ts exactly at post time
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","text":"deploy starting..."}' \
| jq '{ok, channel, ts}'
{
"ok": true,
"channel": "C0123456789",
"ts": "1720526400.001900"
}
Store ts as a STRING with full precision — never parse it into a number.
Step 2: Reproduce the failure with a mangled ts
curl -s -X POST https://slack.com/api/chat.update \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","ts":"1720526400.0019","text":"deploy done"}' | jq .error
"message_not_found"
Dropping the final 00 changed the timestamp; the pair no longer matches.
Step 3: Verify the message exists with the exact pair
curl -s -G "https://slack.com/api/conversations.history" \
--data-urlencode "channel=C0123456789" \
--data-urlencode "latest=1720526400.001900" \
--data-urlencode "inclusive=true" \
--data-urlencode "limit=1" \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
| jq '.messages[0] | {ts, text}'
{
"ts": "1720526400.001900",
"text": "deploy starting..."
}
If this returns nothing, the message was deleted or the channel/ts is wrong.
Step 4: Update with the exact, unmodified values
curl -s -X POST https://slack.com/api/chat.update \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","ts":"1720526400.001900","text":"deploy done ✅"}' \
| jq '{ok, ts}'
{
"ok": true,
"ts": "1720526400.001900"
}
Example Root Cause Analysis
A deploy bot posts a “starting” message, saves the ts to a job record, then updates it to “finished”. The update fails intermittently:
curl -s -X POST https://slack.com/api/chat.update \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","ts":1720526400.0019,"text":"finished"}' | jq .error
"message_not_found"
The job record stores ts in a JSON number field, so 1720526400.001900 is serialized back as 1720526400.0019 — the trailing zeros and full precision are lost. The fix is to persist ts as a string everywhere:
curl -s -X POST https://slack.com/api/chat.update \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","ts":"1720526400.001900","text":"finished"}' | jq .ok
true
Keeping the timestamp a string preserves the exact value and the update succeeds every time.
Prevention Best Practices
- Always store and pass
tsas a string with full precision; never round-trip it through a float/number type. - Persist the
(channel, ts)pair together and pass both to every follow-up call. - For threaded replies, keep each reply’s own
tsdistinct from the parentthread_ts. - Before updating, tolerate a missing message: catch
message_not_foundand decide whether to repost or no-op. - Remember
chat.postEphemeralmessages cannot be updated or deleted — track them differently. - For ad-hoc triage, the free incident assistant can spot a truncated timestamp in a failing update. See more in Slack guides.
Quick Command Reference
# Post and capture the exact ts (string)
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","text":"hi"}' | jq -r '.ts'
# Verify (channel, ts) points at a real message
curl -s -G "https://slack.com/api/conversations.history" \
--data-urlencode "channel=C0123456789" \
--data-urlencode "latest=1720526400.001900" \
--data-urlencode "inclusive=true" --data-urlencode "limit=1" \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" | jq '.messages[0].ts'
# Update using the exact string ts
curl -s -X POST https://slack.com/api/chat.update \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","ts":"1720526400.001900","text":"done"}' | jq .ok
Conclusion
message_not_found means the (channel, ts) pair does not resolve to a message the token can act on. The usual root causes:
- A reformatted or precision-lost
ts(stored as a number). - Pairing the
tswith the wrong channel id. - The message was already deleted.
- Confusing a reply’s
tswith the parentthread_ts. - Trying to update/delete an ephemeral message.
Keep ts a full-precision string, always pair it with its original channel, and verify the message exists before acting on it.
Fixed it? Get 500 Slack & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.