I removed the account wall from checkout. The 48-hour claim link is where the risk lives. A developer removed the mandatory account wall from a subscription checkout, letting buyers pay with only an email address, and built a 48-hour signed claim link to hand the paid workspace to its owner. The token is base64url-encoded JSON carrying the payment request id, checkout email and expiry, signed with HMAC-SHA256 using the app's existing auth secret, with signature verification performed before the payload is parsed. Single use is enforced by organization membership rather than a token table: a claim is refused with 410 if the guest organization already has a member. For a year my checkout had a wall in front of it: an account was required before payment. The reasoning was simple — a subscription has to belong to somebody. The cost was less simple. People who had already decided to buy were asked to invent a password first, and some of them left at exactly that moment. The most expensive step of a funnel is the step that asks for something other than money. So I removed the wall. Anyone can now complete a purchase with nothing but an email address. This post is about the second half of that change, which is the part that actually matters: after the money arrives, how does the buyer get the workspace? Half one is checkout. An unauthenticated request creates a guest organization, starts a pending billing row, and sends the buyer to the payment provider with a success URL that points at a claim page. No password, no session. Half two is the handoff. The payment webhook activates the billing row and upgrades the guest organization — but nobody is logged in as that organization. It exists, it is paid, and it is empty. If I stop there, I have taken money and delivered a row in a database that no human can reach. That is worse than the login wall. The naive fix is to match accounts by email: when someone signs up with the email used at checkout, attach the workspace automatically. I rejected it. In B2B, the person who pays is often not the person who uses the product — a founder buys, an operations lead lives in it. Email matching also means anyone who can control that address at any later date inherits a paid workspace, with no expiry and no record of the transfer. What I needed instead was an explicit artifact: something the buyer receives, that proves "this order belongs to the holder of this mailbox", expires on its own, and works exactly once. The claim link carries a token with a payload of three fields: r — the payment request id, which identifies the order e — the email address used at checkout, lowercased x — an expiry timestamp, 48 hours after the link is generated The token is base64url JSON payload followed by an HMAC-SHA256 signature of that string, using a secret the app already has. Verification checks the signature first and only then parses the payload. That ordering is deliberate: until the signature passes, the payload is just bytes from a stranger. Three properties fall out of this design: Tampering is useless. The email sits inside the signed payload. Changing it to another address invalidates the signature, so "claim someone else's order by editing the link" gets a 403 instead of a support ticket. Expiry needs no cleanup job. The timestamp is compared against the clock at verification time. There is no token table to sweep and no background worker to forget to deploy. The secret is already there. I reuse the application's existing auth secret, with an environment variable that can override it for rotation. Inventing a second secret is how you end up rotating one and forgetting the other. The interesting question is "has this link been used?" A token table would answer it directly, and would also mean a schema migration on a live database for a feature that might see three uses this month. I did not want that trade. So I defined single use by the effect itself rather than by a record: claiming means attaching the signed-in user to the guest organization. If the organization already has a member, the claim is refused with 410. The state that decides reuse is the same state the product already needs — organization membership — so the two can never disagree. A token cannot say "unused" while the workspace already has an owner. The claim flow itself is two steps, and the split is deliberate: p @example.com and a plan name. A claim flow is mostly a list of things it refuses to do. Mine, with the status codes I return and why: None of these mutate money. The billing row was already active when the webhook arrived; claiming only moves a membership. That separation is what let me ship the whole thing without touching the payment code path. No new table. The token is stateless and the single-use check reads existing membership. Zero migrations for this feature. No passwordless login. The claim link proves ownership of an order. It says nothing about who controls the account, and the buyer still signs in with any method the app supports before the two addresses are compared. Mixing the two would mean a leaked link is also a leaked session. No "claim by email later" fallback. If the order has no email on file, the page says so and asks the buyer to reply to the receipt. A human attaching that order is slower, but an automatic rule for unattached orders is a rule for stealing them. If you are removing an account wall from a payment flow, the wall does not disappear. It moves behind the transaction, and it becomes an artifact with four properties: bound to the order, bound to the payer, self-expiring, and single-use. The artifact is cheap to build; the discipline is in refusing to add any path that bypasses it. The other lesson is about ordering. I shipped the claim flow with the guest checkout, in the same change. An account wall removed alone is a funnel improvement that silently converts into unpaid-for promises — money in, workspace unreachable. If you cannot describe how the buyer gets access after paying, you have not finished removing the login wall; you have just moved it to your support inbox. SaaSClerk audits SaaS subscriptions, seat counts and renewal dates in one ledger, so the money you spend matches the seats people actually use: saasclerk.app https://www.saasclerk.app?utm source=devto&utm medium=article&utm campaign=guest claim