---
name: checkouts
description: Add, change or check a project's card checkout following checkouts.md, with Railbed as the provider. Use when the person asks to take payments, add a checkout, a Pay or Buy button, a paywall or paid plans, sell something, accept cards, connect Railbed or set up payment webhooks, or mentions Railbed or checkouts.md; and whenever the project has a CHECKOUTS.md file and the task touches payments, orders or webhooks.
---

# Checkouts

checkouts.md is an open format for how a project takes money, and Railbed is its default provider: buyers pay by card, the project fulfils each paid order exactly once, and `CHECKOUTS.md` at the project's root records how. The full procedure is https://checkouts.md/playbook.md; read it, and the recipe for the stack, before writing code. If the project has a `CHECKOUTS.md`, read it before touching payment code, and keep it current.

## Rules

- **R1** Secrets never enter chat, logs or git. Only their names go in CHECKOUTS.md.
- **R2** Paid means a verified signed webhook, then a re-read of the payment by id. Never the redirect back.
- **R3** Amounts are decimal strings in the currency's units, like `"49.00"`.
- **R4** Test before Live, and record the evidence.
- **R5** Ask the person only for the account and the approval. Everything else is the agent's job.

## The six steps

First read the project: detect its stack and pick its recipe (`https://checkouts.md/recipes/<stack>.md`), find where its server code runs, and look for `CHECKOUTS.md` at its root. If it exists, follow it and add to it; don't start over ([playbook §1](https://checkouts.md/playbook.md#1-read-the-project)). Then:

1. **Account.** Make sure the person has a Railbed account with a business. The connect link in step 2 lets them sign up (https://app.railbed.io/signup) and set up the business on the way. Live payments also need a payout wallet in Railbed's Settings ([§2](https://checkouts.md/playbook.md#2-the-account)).
2. **Connect.** Run `npx checkouts.md login` with the recipe's options (`--env-path`, never `--env-file`), or use the HTTP connect flow. Give the person the link and the code it prints; they check that the code matches, then approve. The keys go straight into gitignored env files or the host's secret store as `RAILBED_SECRET_KEY`, never into the chat. If you can't store a secret without it showing in the conversation (some app builders), the person creates the keys in Railbed (Developers → Create key) and pastes them into the platform's secrets instead, never into the chat ([§3](https://checkouts.md/playbook.md#3-connect)).
3. **Write CHECKOUTS.md** at the project's root from the template (https://checkouts.md/templates/CHECKOUTS.md), with `mode: test`: what's for sale, what happens after payment, the wiring and the evidence. Keep it current as you go ([§4](https://checkouts.md/playbook.md#4-write-checkoutsmd)).
4. **Wire the checkout** from the recipe: products priced on the server; a route that saves an order, creates a checkout session (`POST https://pay.railbed.io/v1/checkout_sessions` with the order id as `Idempotency-Key` and `reference`) and sends the buyer to its `url`; a Pay button; a success page that only shows status; and a webhook receiver that verifies `Railbed-Signature` against the raw body, re-reads the payment and fulfils each order once. Add the endpoint with `npx checkouts.md webhook <url>`, which stores `RAILBED_WEBHOOK_SECRET` ([§5](https://checkouts.md/playbook.md#5-wire-the-checkout), [§6](https://checkouts.md/playbook.md#6-the-webhook)).
5. **Prove it in Test.** Create a session through the project's own route, then start it and simulate the payment (`POST /v1/checkout_sessions/:id/start`, then `POST /v1/payments/:id/simulate` with `{"outcome":"paid"}`). Check that the signed `payment.paid` arrives and verifies, the order is fulfilled exactly once, a repeated event changes nothing and a forged one gets `400`. Record the evidence in `CHECKOUTS.md` ([§7](https://checkouts.md/playbook.md#7-prove-it-in-test), [§9](https://checkouts.md/playbook.md#9-evidence)).
6. **Go Live.** Add the Live webhook endpoint for the production address (`npx checkouts.md webhook <url> --live`, with the same `--live-to` as `login`), put the Live key and that endpoint's secret on the production host without printing them, deploy, and set `mode: live` in `CHECKOUTS.md`. The first real payment is the Live evidence ([§8](https://checkouts.md/playbook.md#8-go-live)).

Then report back in plain words: what changed, what you verified and how, what's left, and where the person sees payments and revokes keys ([§11](https://checkouts.md/playbook.md#11-report-back)).

## Facts to get right

- API base `https://pay.railbed.io/v1`, `Authorization: Bearer $RAILBED_SECRET_KEY`, from server code only: there is no publishable key and no CORS. `rb_test_…` is simulated (no money moves), `rb_live_…` is real.
- Amounts `"1.00"` to `"100000.00"`, as strings; currencies `USD`, `EUR`, `GBP`, `CAD`, `AUD`. Test mode's lowest provider minimum is 2.00 USD: below it, starting a payment answers `409 no_providers`.
- Session and payment share one id, `pay_…`. Statuses: `open` and `pending` (wait), `paid` (fulfil once), `held` (don't fulfil; the business reviews it), `failed`, `expired` (can still become paid if money arrives late).
- `Railbed-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">`, keyed with the whole `whsec_…` secret. Accept any matching `v1` (two for 24 hours after a secret roll), reject a `t` more than 300 seconds off, and answer `2xx` within 10 seconds; a redirect counts as a failed delivery.
- A webhook's `data.payment` is camelCase with millisecond times (the event's own `created` is in seconds); the API is snake_case with Unix seconds. Re-read the payment with `GET /v1/payments/:id` and use that one shape.
- Errors are `{ "error": { "code", "message", "field"? } }`. Branch on `code` and the HTTP status; the playbook's §10 lists every code and its fix.

## Commands

| Command | Does |
|---|---|
| `npx checkouts.md login [--test-only] [--env-path .env] [--live-to env:<file>\|wrangler\|vercel]` | Connects after the person approves. Saves the Test key to the env file and the Live key to `--live-to` (default `env:.env.production`), both as `RAILBED_SECRET_KEY` |
| `npx checkouts.md webhook <url> [--live] [--events payment.paid,…]` | Creates the endpoint with the saved Test key (the Live key with `--live`) and stores `RAILBED_WEBHOOK_SECRET` beside it |
| `npx checkouts.md test [--amount 5.00]` | A Test-mode smoke test: session, start, simulate paid, re-read until paid, and an evidence line. It doesn't go through the project's own route or check the delivery |
| `npx checkouts.md status` | Which keys and secrets are present (masked), whether the keys work, and whether `CHECKOUTS.md` exists |
| `npx checkouts.md skill` | Installs this skill for the project's coding agents |

`webhook`, `test` and `status` read the keys from the files `login` wrote, so pass them the same `--env-path` and `--live-to`. Add `--json` to any command for JSON Lines; no output ever contains a secret. Exit codes: 0 done, 1 failed (the reason is printed), 2 a usage mistake, 130 stopped with Ctrl-C. `login` waits up to 15 minutes for the approval, so run it in the background or with a long timeout and read the link and code from its output (with `--json`, the `approval_needed` line).

## Links

- Start: https://checkouts.md/index.md · Procedure: https://checkouts.md/playbook.md · Format: https://checkouts.md/spec.md
- Everything in one file: https://checkouts.md/llms-full.txt
- Railbed's API reference: https://railbed.com/docs/llms.txt
