x402 Paid Anonymous Links: One Payment Buys One Link
smo1 advanced 8 min read
ELI5
Someone without an account can pay a tiny crypto payment to make one short link. Cloudflare sits in front and turns purr-api’s “please log in” into “please pay”. The caller pays and retries with a receipt. purr-api checks that the request really came through Cloudflare, checks the receipt itself, asks a payment verifier to confirm it, and only then makes a 30-day link owned by a special account nobody can log into. The same receipt can never buy a second link.
Technical Deep Dive
Source: smo1-io docs/decisions/2026-10-12-x402-paid-anonymous-link-create.md (status accepted, sign-off flight@arno.host 2026-10-14) (1f19667); purr-api internal/handler/paid_link_handler.go:24-33, 86-257, internal/x402/provenance.go:10-20, internal/x402/payment.go:116-172, internal/service/link_service_paid.go:40-97, internal/config/config.go:91, 164-178, 222-258, internal/router/router.go:154-164, internal/middleware/auth.go:628-640, cmd/server/main.go:265-303, migrations/000011_x402_paid_create.up.sql (origin/main bb6cfc2), read 2026-10-25.
Flow
sequenceDiagram autonumber participant C as Keyless caller participant G as Cloudflare zone and Gateway participant P as purr-api POST /api/links participant F as x402 facilitator C->>G: POST purr.smo1.io/api/links with url G->>P: request plus provenance header from a Transform Rule P-->>G: 401 Authentication required G-->>C: 402 with x402 payment terms C->>G: retry with X-PAYMENT proof G->>P: request, provenance header, X-PAYMENT P->>P: provenance, per-IP limit, body allowlist, decode, replay lookup, local checks P->>F: verify payment F-->>P: valid P->>P: one transaction, link and x402_payments row under the daily cap P-->>C: 201 short URL and payment_refThe Gate
With X402_PAID_CREATE_ENABLED on, POST /api/links is registered as PaidLink.Gate() → RequireAuth → tier limit → CreateLink (router.go:158-164). The Gate passes a request through untouched only when it carries a credential and no payment header. Any request with a payment header is answered by the paid path even if it also has a stale cookie, because a 401 on a paid request becomes another 402 and the caller pays twice (paid_link_handler.go:101-115).
Status discipline
flowchart TD A[serve] --> B{"provenance header matches, constant time ?"} B -->|No| F403[403 forbidden] B -->|Yes| C{"per-IP limit ok ?"} C -->|No| R429[429] C -->|Yes| D{"body valid, allowlisted fields, at most 4 KiB ?"} D -->|No| B400[400] D -->|Yes| E{"payment header present ?"} E -->|No| U401[401, the price signal] E -->|Yes| G{"replay of a created payment ?"} G -->|Yes| OK200[200 original link] G -->|No| H{"local checks and facilitator valid ?"} H -->|No| P403[403 payment refused with reason] H -->|"facilitator down"| S503[503, proof stays retryable] H -->|Yes| I{"under daily cap ?"} I -->|No| C429[429 daily_cap, proof stays usable] I -->|Yes| K201[201 created]- Provenance (
provenance.go): a secret header (default nameX-SMO1-Edge-Auth) that a Cloudflare Transform Rule sets onpurr.smo1.ioonly; at least 32 bytes; compared withsubtle.ConstantTimeCompare; an empty or short secret never matches. Requests topurr-api-production.up.railway.appnever carry it, so the paid path rejects them. - Local payment checks (
payment.go:144-172): schemeexact, network on the allowlist,payToequal toX402_PAY_TO, amount at leastX402_PRICE_ATOMIC, validity window covers now. Then the facilitator, under a global budget ofX402_GLOBAL_VERIFY_PER_MINcalls (default 60). - Body allowlist:
url,display_name,content_classificationand the fiveutm_*fields. Custom slug, domain, password, PIN, hint and redirect mode are refused by construction. - Config fails closed (
config.go:222-258): with the flag on, every price, wallet, asset, network, facilitator URL, cap and limit must be set explicitly, and boot refuses otherwise. There is no default price.
Identity and storage
- Every paid link is owned by user
x402-anon, seeded by migration 000011 with an.invalidemail, no login and no key. Boot refuses to serve paid creates if the row is missing (main.go:271-276).passAuthenticatedrefuses any credential that resolves to it, so nobody can list, edit or read analytics on paid links (auth.go:628-640). - Links are plain redirects, unprotected, active, expiring after
X402_LINK_TTL_DAYS(default 30, max 365), with no tier check (link_service_paid.go:40-97). x402_paymentskeeps one row per decodable attempt, with a uniqueidempotency_keyof (network, payer, authorization nonce), outcomecreatedorrefused, and areceipt_idslot in TOLL’sx402:<network>:<txHash>:<logIndex>format.payment_ref(x402:plus the key) is logged with every refusal for refunds.
Live status
The flag defaults to off (config.go:164). Whether it is on in Railway production is UNVERIFIED; it was not probed. The edge Worker on smo1.io is guarded by a regression test that it never answers 402 (zoomies-edge/src/handlers/redirect.no402.test.ts), and purr-api has the same guard on its redirect handlers.
Key Terms
- x402 — HTTP 402 payment protocol: terms in the 402, proof in a retry header
- Monetization Gateway — Cloudflare feature that rewrites an origin 401 into a priced 402 (waitlist-only per the decision)
- Provenance header — shared secret proving a request crossed the Cloudflare zone
- Facilitator — external service that confirms an x402 payment proof
- x402-anon — the system owner of every paid link; unusable as a login
Q&A
Q: Why does a paid request never get a 401 from purr-api? A: The Gateway converts any 401 into a 402, so a 401 on a request that already carries a payment would charge the caller again. Only a request with no payment header gets the 401; payment and provenance failures are 403, caps and limits 429.
Q: What two checks must both pass before a paid link is created? A: Provenance (the Cloudflare-set secret header, compared in constant time) and payment (local checks on scheme, network, payee, amount and validity window, then facilitator verification).
Q: What happens if the same payment proof is presented twice?
A: The idempotency key is unique, so the second request is answered from the first: 200 with the original link, or 403 if that proof was refused before, or 410 if the link it bought was taken down. No second link is created.
Q: Is the paid path live in production?
A: Unknown. The code is merged and the decision accepted, but X402_PAID_CREATE_ENABLED defaults to off and its production value was not checked (UNVERIFIED).
Examples
A request straight to the Railway hostname with a valid-looking X-PAYMENT header gets 403 forbidden before the body is even read, because only the Cloudflare rule on purr.smo1.io adds the provenance header. The same request through purr.smo1.io on a day the cap is already spent gets 429 with reason daily_cap, stored unkeyed so the proof can buy a link after the UTC midnight reset.
neighbors on the map
- Rate Limiting: Edge per-IP, Origin per-Tier debugging 429 errors for specific users
- Billing via TOLL: Decision Accepted, Polar Still in Code debugging why a user's tier did not update after payment
- Database Architecture writing a migration or a production SQL check