Why pasting a burn-after-reading link into a chat that generates a preview does not always use up the read
A card under the message is not the same as the recipient opening the ciphertext. To draw that card, Slack, Discord, or another chat product sends an HTTP GET to the landing page. That fetch usually reads the title and a short description. It does not click Open and view, and it does not send the key after #. This article splits “the card appeared” from “one read was consumed,” then walks a check you can run on a dummy link. It is not a how-to for the Burn-Link create form.
First, which layer the card fetches
This article only answers why a burn-after-reading link pasted into a preview-generating chat is not always already used up. It is not the form on Burn-Link, and it is not “why # never enters server logs.”
What layer a preview card actually fetches
The card appears a few seconds around send. The request comes from the chat product’s servers, not from the recipient’s finger.
Paste a URL that looks like https://…/s.html?id=…#… into Slack, Discord, or Microsoft Teams and a small card often appears under the composer or the message: a title, a one-line description, sometimes an icon. Many people read that card as “the link has already been opened.” The order is the other way around. The chat product first sends an HTTP GET to that address, pulls og:title, og:description, or a plain <title> from the HTML, then draws the card. The recipient may not have clicked anything yet.
Slack wrote this down. By default, messages that contain a fully qualified URL expand into a preview. The robot that does the expansion identifies itself as Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots). Slack’s Unfurling links in messages page calls that default treatment “classic link unfurling”: Slack crawls the URL and attaches a preview. The same company’s Slack Robots page says the expander fetches as little of the page as it can (using HTTP Range headers) and looks for oEmbed, Twitter Card, and Open Graph tags. It fetches an image, video, or audio file only when a tag points at one, so it can check that file. Responses are cached globally for around 30 minutes. The page does not say “run the page’s JavaScript” or “click a button for the user.”
When a browser — or a preview robot that behaves like one — issues that GET, text after # is not part of the request line. IETF RFC 3986 §3.5 defines the fragment as client-side identification. It is not used by the server when processing the URI. So even if the robot copies the whole string you see in the composer, the request that hits UsePwd usually stops at s.html?id={id}. Why the key never appears in access logs is the older article Why URL hash fragments never reach the server. This piece covers the next layer: does a GET that never carried the key count as “one read.”
The useful question is not “did the chat product see this URL.” It is “did this GET reach the ciphertext API.” A card only proves the title was read. Whether ciphertext is still there is a separate check.
Why the first GET is not a read
If a tool treats “fetch ciphertext” and “open the landing page” as the same request, a preview bot will spend the only read. Split those steps and they are no longer the same action.
Plenty of one-time links hang the secret on the same URL the preview bot GETs. The robot fetches once; the server returns the body and deletes the record. The recipient then opens “already read” or a 404. That failure is common. It is not Slack “maliciously burning the secret.” It is an implementation that treats a cacheable GET — the method RFC 9110 still describes as safe — as the action that destroys ciphertext.
UsePwd Burn-Link splits the job into three steps. On create, the current tab encrypts with Web Crypto and AES-256-GCM. Plaintext is at most 32 KB. The only fields uploaded are ciphertext, a TTL, and a read count. Expiry can be 1 hour, 24 hours, 7 days, or “burn after read only” with no clock TTL. Reads are 1–10, default 1. The share URL looks like s.html?id={id}#{key}. Create and read both open immediately — neither side registers. The server only stores ciphertext. It cannot see plaintext, and there is no account that could recover the secret by user.
When the recipient opens the read page, the tab first asks /api/secrets/{id}/status whether ciphertext is still there. That call returns active, burned, or expired. It does not increment the read count and it does not delete the record. Only after Open and view does the browser request /api/secrets/{id}, fetch ciphertext, and decrypt locally with the key after #. After the create-time count, the server deletes the ciphertext; a later fetch returns 410. A preview bot that stops at the landing HTML usually has no key and does not click that button.
Returns the static read page. The title is “Open a One-Time Secret Link.” It does not contain plaintext. Preview cards usually stop here.
Only answers still here, burned, or expired. Repeating the check does not add one to the read count.
This is the call that counts as a read. After the default of 1, ciphertext is deleted. On the read page that is Open and view.
The read page also prints a sentence you can check on the spot: the button “may consume one read.” Without scripts there is no decrypt path; a <noscript> block says so. A preview bot that only reads HTML and does not run JavaScript will not even send the status check. If an in-app browser does run scripts, it still stops at “Ciphertext is still here” until someone clicks the button.
Which bots arrive, and what they take
Many products can draw a card. What they take is the page header, not the key after #, and not decrypted plaintext.
Slack’s link-expanding robot, Discord’s embed crawler, and the server-side request Microsoft Teams uses to build a card are the same class of fetch: GET the page, then look for a title and a description in the HTML. Discord identifies that crawler as Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com). Public write-ups of Discordbot agree on one limit you can treat as a working assumption: it parses the HTML the server returned and does not execute JavaScript. Slack is explicit about another limit. After trying robots.txt, Slack stopped honoring it as if it were a search crawler, because unfurling is “acting on behalf of a human,” not walking the site. Putting noindex on the read page can keep search engines from indexing it. It will not stop the card in a channel.
UsePwd’s read page is a temporary ciphertext view. It ships noindex, nofollow and is not in the sitemap. The document title is fixed as “Open a One-Time Secret Link.” The description only says decryption finishes in this tab and the key sits after #. If a preview card appears, the channel sees that generic line — not the password, token, or ticket text you typed. That can be true at the same time as “the access log has no fragment.” The card proves someone GET the landing page. The log still has no key after #.
Slack says unfurl results for the same URL are cached for around 30 minutes. Pasting the same test link into a channel twice in a short window may not produce a second fetch. To check “did a bot visit,” look for a User-Agent such as Slackbot-LinkExpanding or Discordbot in your own access log. Do not stare at the card refreshing. A GET to s.html still does not mean /api/secrets/{id} was called.
When a read really is used up
“Not always used up” is not “never used up.” A visit that can click Open and view is the visit that reaches the ciphertext layer.
The usual consumer is the recipient: they open the full link, see “Ciphertext is still here,” then click Open and view. The default count is 1. After that successful fetch, the server deletes the ciphertext. Opening the same link again shows “Already burned.” If you set reads to 2 or higher at create time, earlier fetches leave ciphertext in place until the counter hits the cap. Expiry also deletes: 24 hours by default, or 1 hour or 7 days. “Burn after read only” deletes on count, not on the clock.
The second class is an environment that runs scripts and simulates a click. Some mail security gateways and link sandboxes open the URL in an isolated browser. Some of them also click the primary button. That is no longer “draw a card.” It is a second, human-shaped visit. If the sandbox clicks Open and view, the read is used and the recipient later sees the burned state. Check that against the gateway’s own report. You cannot infer it from the chat card.
The third class is posting the ciphertext API URL itself. The read-page address is s.html?id=. The call that consumes a read is /api/secrets/{id}. Ordinary users do not copy the second one. If someone pastes that API URL into a channel that previews links, and the product treats the first GET as the fetch, the read can be spent at preview time. UsePwd’s share shape is not that address. The create page copies the read-page link with #.
A preview that did not spend a read does not mean the secret exists only for the recipient. The chat product still stores the full URL. A workspace admin may still export history. The read limit controls how long ciphertext lives on the server. It does not control who can still open that address from the channel.
How to check a preview on the spot
The goal is not to prove “no chat product on earth will ever read ciphertext.” It is to prove that after the card appears, the ciphertext API has not been called yet.
-
01
Prepare dummy text you will not use for a real handoff
Do not type a live API key or a login password. Open Burn-Link, enter
PreviewCard-20260905, leave reads at the default of 1, and leave expiry at 24 hours. The page opens immediately — no sign-up. These steps exist to compare a card with the APIs. They are not a way to hand this string to a coworker as a real secret. -
02
Save the full link, then open the read page yourself
After create you should see both
s.html?id=and a#segment. Open that same link in the current browser. The page should stop at “Ciphertext is still here” and show Open and view. Do not click the button yet. Press F12 and open the Network panel. You should see a request to/api/secrets/{id}/status. You should not yet see a ciphertext fetch to/api/secrets/{id}without the/statussuffix. -
03
Paste the same link into a preview-generating test channel
Use a Slack channel only you can see, a Discord DM to yourself, or another test room. Wait for the card. The title should be close to “Open a One-Time Secret Link.” It should not show
PreviewCard-20260905. Refresh the read page, or open the same link in a window that has not clicked the button. It should still say “Ciphertext is still here.” If it already says “Already burned,” some other visit reached the ciphertext API. That is not “the card itself equals a read.” -
04
If you have access logs, match the User-Agent
In the preview window you typically see a GET to
/en/s.html?id=…. The User-Agent may containSlackbot-LinkExpandingorDiscordbot. In the same window you should not see a successful fetch of/api/secrets/{id}. Slack caches the same URL for around 30 minutes, so a second paste in that window may not produce a second fetch. -
05
Only then click Open and view, and confirm the read is used
Click the button now. The Network panel should show the ciphertext request, and the page should display the dummy text. Close it and open the same link again. It should say “Already burned.” This step exists so “after the button” and “after the card” are two different states you have seen. Drop the link when you are done. Do not reassign it to a real recipient.
If you only need to hand a new key to someone else, do not paste the plaintext into a channel “to see a card.” Keep dummy text and the real secret apart. The real handoff still goes through Burn-Link. For the second-round risk after an environment-variable leak, see After a cloud env-var leak, why the new key should not go back into chat. That article is “do not paste the API key itself into searchable history.” This one is “after the link is pasted, which layer does the preview fetch stop at.”
What chat still keeps if the count did not drop
Reading “the card did not burn the ciphertext” as “this link has left the channel” misses a few limits you can also check.
The chat product stores the whole address you pasted. Anyone who can search history, export a workspace, or open a backup can open the read page again. While reads remain, a later opener can still click Open and view and see plaintext. After the count is spent, history keeps a burned link plus whatever title the card showed. An admin export cannot restore ciphertext the server already deleted. It also cannot undo “this channel once held the full URL.”
Screenshots and forwards are another copy. If someone captures plaintext after a successful read, or forwards the link into another preview-generating room, the boundary moves from “this GET” to “who is on the next hop.” Burn-Link limits how long ciphertext lives on the server and how many times it can be fetched. It does not stop the recipient from taking a screenshot. UsePwd has no accounts and no vault, and it cannot recover a lost link by user. Long-term storage or multi-device sync belongs in a dedicated password manager. Identity is on About.
Files are a third path. A local backup of a single file up to 5 GB goes through File Encryption Box, which writes .lock / .enc. The file is not uploaded by default. Generate the passphrase itself in the 6–128 range on the password generator, then check strength on Password Audit. Those pages, like this article, open immediately. Do not read “the preview did not spend a read” as “paste this into any channel and it is safe.” Channel history and the preview fetch are not the same layer.
What people ask after a preview
These four answers stay on this article’s boundary. They do not repeat the buttons on the create page.
When you finish, check one preview card
This article answers why a preview does not always use up the read. To generate a dummy link you will not use for a real handoff, then paste it into a test channel, open Burn-Link. You do not register first.