Back to Blog
Guide

Slack Message Permalink: Format, chat.getPermalink and ts, Tested

A Slack message link is /archives/<channel>/p<ts without the dot>. We copied links in Slack web and called chat.getPermalink on 1 October 2026 to compare the formats for channel messages and thread replies, then turned links back into a channel and ts.

Slack Green Team
October 1, 2026
October 1, 2026
3 min read
Share:
slack api
developers
python

A Slack message link has the form https://<workspace>.slack.com/archives/<channel ID>/p<ts>, where the part after p is the message's ts with the dot removed. A link to a thread reply adds ?thread_ts=<parent ts>&cid=<channel ID>. You get one from the message menu (More actions > Copy link) or from the API with chat.getPermalink. We did both on 1 October 2026 in a test workspace, for a channel message, a thread parent and a thread reply, and the two ways do not always return the same string.

Slack web: the More actions menu with Copy link, on a channel message and on a reply in the thread pane

These are the links Slack gave us for three messages in one channel, C0C5QDNUJ7M:

MessageCopy link in Slack webchat.getPermalink
Message with no replies/archives/C0C5QDNUJ7M/p1790838379966019same
Thread parent (1 reply)/archives/C0C5QDNUJ7M/p1790838372599559same, plus ?thread_ts=1790838372.599559&cid=C0C5QDNUJ7M
Reply in that thread/archives/C0C5QDNUJ7M/p1790838372960849?thread_ts=1790838372.599559&cid=C0C5QDNUJ7Msame

To read a link:

  • • C0C5QDNUJ7M is the channel ID. How to find a Slack channel ID covers the other places it shows up.
  • • p1790838372960849 is the message ts 1790838372.960849. Put the dot back after the 10th digit. The first 10 digits are a Unix time in seconds; paste them into the Slack timestamp tool to see the date.
  • • thread_ts is the ts of the thread's first message, and cid repeats the channel ID.

The one difference we saw: on a thread parent, chat.getPermalink added thread_ts (equal to the parent's own ts) while Copy link did not. Both links open the same message, so do not compare permalinks as strings to decide whether two links point at the same message. Compare channel and ts.

chat.getPermalink takes channel and message_ts. Our bot token had chat:write and channels:history; Slack's method reference lists no required scope. Real responses:

{"ok": true, "permalink": "https://slack-0yr1948.slack.com/archives/C0C5QDNUJ7M/p1790838379966019", "channel": "C0C5QDNUJ7M"}
{"ok": true, "permalink": "https://slack-0yr1948.slack.com/archives/C0C5QDNUJ7M/p1790838372960849?thread_ts=1790838372.599559&cid=C0C5QDNUJ7M", "channel": "C0C5QDNUJ7M"}

The errors we got for wrong input:

message_ts or channel we sentResponse
The p-number from the link, p1790838379966019message_not_found
The ts without its dot, 1790838379966019message_not_found
A ts with the right seconds but wrong decimalsmessage_not_found
A channel ID that does not existchannel_not_found

So message_not_found usually means the ts is not the exact string with its dot and six decimals. If you store ts as a float, you lose digits and get this error.

Never appear "away" on Slack again

Cloud-based. No downloads. Works 24/7 even when your laptop is off.

This is the parser, then the script we ran against the three API links:

import re
from urllib.parse import urlparse, parse_qs

def parse_permalink(url):
    u = urlparse(url)
    m = re.search(r"/archives/([A-Z0-9]+)/p(\d{16})", u.path)
    channel, digits = m.group(1), m.group(2)
    ts = digits[:10] + "." + digits[10:]
    thread_ts = parse_qs(u.query).get("thread_ts", [None])[0]
    return channel, ts, thread_ts

Fetching the message needs two different calls. A top-level message comes from conversations.history with latest set to its ts. A thread reply only comes from conversations.replies:

def fetch_message(url):
    channel, ts, thread_ts = parse_permalink(url)
    if thread_ts and thread_ts != ts:
        r = client.conversations_replies(channel=channel, ts=thread_ts, latest=ts, inclusive=True, limit=1)
        return [m for m in r["messages"] if m["ts"] == ts][0]
    r = client.conversations_history(channel=channel, latest=ts, inclusive=True, limit=1)
    return r["messages"][0]

Output with a slack_sdk bot client:

('C0C5QDNUJ7M', '1790838372.599559', '1790838372.599559')
 -> 1790838372.599559 'Permalink test: parent message'
('C0C5QDNUJ7M', '1790838372.960849', '1790838372.599559')
 -> 1790838372.960849 'Permalink test: reply in the thread'
('C0C5QDNUJ7M', '1790838379.966019', None)
 -> 1790838379.966019 'Permalink test: message with no replies'
history with the reply ts -> 1790838372.599559 'Permalink test: parent message'

The last line is the trap. We called conversations.history with the reply's ts and it returned the thread parent, with ok: true and no warning, because the channel history holds only top-level messages. If your code "fetches the wrong message" from a reply link, this is why. A reply link copied without its ?thread_ts= part has the same problem: the parser cannot tell it is a reply. Reading whole channels and threads is covered in Slack API: get messages from a channel, and posting into a thread in Slack thread_ts, tested.

FAQ

No. The ts stays the same after an edit, so the link stays the same.

No. An incoming webhook returns only ok, with no ts, so you cannot build the link. Post with chat.postMessage instead, which returns channel and ts, then call chat.getPermalink.

Always Active

Stop Jiggling Your Mouse.

Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.

Related Articles

Guide

Slack Interactivity Payload: Real block_actions JSON and response_url Limits

We clicked a button, a static_select and a datepicker in Slack on 1 October 2026 and logged the block_actions payloads our app received. Then we measured response_url: 5 posts, then used_url; it worked at 29:50 and was dead at 30:10.

Slack Green Team
Guide

Slack unfurl_links and unfurl_media: What Each Flag Does, Tested

We posted a web page, a YouTube video and an image with every combination of unfurl_links and unfurl_media on 1 October 2026, then built a custom preview with link_shared and chat.unfurl. The flags do not split the way the names suggest.

Slack Green Team
Guide

Slack Scheduled Message Didn't Send? 5 Cases We Tested

We scheduled five messages for 4:40 PM on 1 October 2026, then archived, left and deleted their channels and deleted a thread parent before the send time. Two sent, three never did, and Slack gave no notice about the three.

Slack Green Team