Your Order Link Is the Key: What It Exposes
We created a real 100 USDT order on 3 November 2026 and read it back with no cookie, no auth and no rate limit: ten fields, no expiry, and one third-party script that receives the full URL. A measured look at what an order link is.
At 10:03:11 UTC on 3 November 2026 we created a real order on veilswap.io: 100 USDT on TRON in, Monero out. The response was five fields long — id 4192151d-…, deposit address TUTmcejZAXe2nGhoJ6CJ5Fz8qHn9QMuvTs, 0.13951129 XMR, status waiting_deposit, and a deadline of 10:13:12, 601 seconds later. Everything that follows that moment happens through one string: https://veilswap.io/order/4192151d-04c6-4595-936c-48b15a4ad0c6. No account, no email, no password. The link is the credential, and this piece is about what it hands over, to whom, and for how long. (XMR to USDT exit)
The design is deliberate, and so is the cost. An exchange without accounts cannot ask you to log in, so it has to hand you something you can carry: a URL. The orders table has twenty columns and none of them is email, phone or name, which means there is no second channel to recover an order from — and no second identifier tying you to a purchase. That is the trade: one fewer link between you and a swap, one more secret you have to hold. The strength is real. All 143 orders in the production database are uuid v4, which is 122 bits of randomness; at a billion guesses per second the expected time to find one is about 1.7 x 10^20 years. Guessing is not the attack. Carrying is.
What the link actually hands over. A GET to /api/order/<id> with no cookie and no Authorization header returned 200 and ten fields: depositAddress, depositExtraId, fromAmount, fromAsset, toAsset, toAmount, status, providerStatus, validUntil and id, with cache-control: no-store. It does not return to_address or refund_address; both stay server-side. After the order reaches a terminal state the same endpoint also stops returning payinHash and payoutHash — we checked a paid order and both keys were gone. So a leaked link tells a stranger what you are swapping, how much, which deposit address to watch and how far along it is. It does not tell them where the coins went, and it does not hand them the transaction hashes once the swap is done.
Only the payment window expires, not the link. That 601 seconds is the funding window. At 10:04 UTC we read back an order created at 09:24:55 that had already been refunded, and it still returned its deposit address and validUntil, 39 minutes after creation. The code agrees: the order route exports GET only, with no PATCH, PUT or DELETE, so there is no rotation, no revocation and no delete-order button. One more detail makes this sharper. Our 143 orders map to 134 distinct deposit addresses, and two of those addresses each took seven and four orders; a link pointing at one of them is a pointer to a shared address, not to a private one. That reuse is the pool's doing, not a bug in the link, but it changes what the link reveals: not just your order, but an address several orders passed through.
Three local copies survive outside our reach. Browser history keeps the URL, and browser sync copies it to every signed-in device. A screenshot of the order page usually contains both the QR code, which encodes the deposit address, and the address bar. And the swap widget writes vs_active_order into localStorage with the order id, deposit address, extra id, both amounts and both asset ids; it is deleted only when a terminal status arrives. Close the tab on a shared laptop before paying and yesterday's deposit address is still sitting in that profile — sessionStorage's vs_order_session only decides whether /exchange auto-restores the old order, not whether the record is kept. None of these three copies is visible to us, and none of them can be revoked from our side, which is why the link has to be treated as something you carry rather than something the site guards.
The page ships one third party that sees the URL. The /order/<id> HTML is 32,795 bytes, carries a robots meta tag with noindex, nofollow, sets no cookie, and returns cache-control: no-store. It loads ustat.js, 3,560 bytes, from 01a0d6f6-4444-7a6d-a45f-6375a01eba8f.spst2.com. Reading that script: it takes location.href into a url field, collects referrer, screen size, language and a canvas fingerprint, and POSTs the bundle to /api/report. Clarity, which runs on the exchange page, is absent here — zero occurrences in the order HTML — but the smaller analytics script still receives the complete order URL. The referrer-policy of strict-origin-when-cross-origin trims the Referer header to the origin for cross-site requests; it does not stop a script you loaded from reading location itself. This is the one disclosure path that leaves our infrastructure entirely.
On our side the link lands in a log line. Our nginx format records remote address, the real client IP from CF-Connecting-IP, timestamp, the full request line including the UUID, status, bytes and user agent. Logs rotate daily with 14 archives kept, so about 15 days of IP-to-order mapping exist at any time. In that window we counted 83 order-page opens, 738 API polls and 149 distinct order ids; 61 distinct order paths, of which 6 were fetched from more than one client IP, and 8 orders polled from more than one address, the record being three. Four paths were fetched by a bot user agent — including TelegramBot (like TwitterBot) — and three of those also carried a human UA, which is what a link pasted into a chat looks like from the server side: the preview bot arrives within minutes, before the recipient clicks anything. On the 404 side, 120 requests across 82 paths came from 9 IPs, the busiest hitting /api/order/1 and /api/order/callback — a generic scanner, not enumeration. We saw no successful brute force, and at 122 bits we do not expect to.
What a stranger can and cannot do with it. They cannot change the payout address, change the refund address, cancel the order or move funds: there is no write endpoint, and both addresses are frozen at creation. Sending coins to the deposit address simply pays your order. They can read the amount and corridor, watch the deposit address on-chain, and poll the status as often as they like — 40 rapid GETs returned 40 responses of 200, because there is no application-layer rate limit on reads, though quotes are limited and order creation is capped at six per hour per IP. The realistic damage is disclosure plus social engineering. Someone who knows your order id, the amount and the fact that it is still waiting can approach you as support with details only an insider should have, or sit on the deposit address and watch when it gets funded. That last one is the quiet part: a watched deposit address reveals your timing, and timing is often enough to connect a swap to whatever came before it. (recovering misdirected funds)
A routine that costs nothing. First, never paste the whole URL into a chat, a support ticket or a screenshot; send the deposit address and the amount, or crop the address bar. Second, if you need to keep the link, put it in a password manager entry named by date and corridor, not only in browser history. Third, when the order reaches paid, expired or refunded, open the page once so the widget clears vs_active_order, then clear site data if the machine is shared. Fourth, use a private window on any computer you do not own. Fifth, if you must ask for help, accept that you will hand over the full id — support cannot look up an order without it, and that is the one place the secret has to travel, so use a channel you would use for a password. Sixth, keep the frame right: the link is a read capability, not a wallet. Losing it does not lose funds, since a paid order still settles, and sharing it does not move funds, since the addresses cannot be changed. Two fixes are worth naming: the analytics script should not run on the order page, because it hands the full URL to a third party, and deposit-address reuse should be shown on the page, because 143 orders currently share 134 addresses.