Checkout
How apps start ATM checkout, pass private session context, and reconcile payment state.
Compatible with the closed-beta ATM app APIs and versioned ATM event headers. Check atm-api-version on every webhook or XRPC receiver event.
Hosted checkout
Hosted ATM checkout is the standard ATM checkout path. It lets ATM own the payment form lifecycle, wallet eligibility, processor session, receipts, proof coordination, return URLs, and fulfillment events.
Embedded ATM checkout is on the roadmap as an optional iframe or JS wrapper. Hosted checkout remains the fallback for wallet-domain rules, redirect methods, OAuth re-auth, browser quirks, and CSP failures.
Creator approval
ATM checks app-recipient approval before it creates a payment row or processor checkout session. The same boundary applies when an app registers fulfillment links for a creator's catalog records. Apps can request approval with money.atmosphere.app.requestRecipientApproval, then send the creator to the returned ATM URL to confirm.
| Missing approval | Checkout returns RecipientAppApprovalRequired. |
|---|---|
| Blocked or revoked | Checkout returns RecipientAppApprovalBlocked. |
| Material change | Checkout returns RecipientAppReapprovalRequired until the creator reviews the change. |
| Self payments | When the app DID and recipient DID are the same account, ATM treats the relationship as self-approved. |
Initiation contract
The public attested.network initiate route stays strict. ATM-specific fields are encoded in the opaque product envelope.
POST /xrpc/network.attested.payment.initiate
Authorization: Bearer <app service-auth jwt>
Content-Type: application/json
{
"product": "atm.checkout.v1:<private-envelope>"
}Checkout envelope
The ATM envelope can include public protocol refs and private checkout context. Apps should keep the raw envelope server-side.
- Public refs
- Product, price, entitlement, discount, and discount-code strongRefs.
- Private session fields
- Payer hint, return URL, cancel URL, app order id, selected options, buyer message flags, and buyer assertions.
- Public-record policy
- Optional checkout override for app records and public network.attested payment proofs.
{
"recipient": "did:plc:creator",
"paymentType": "shop",
"amount": 500,
"currency": "usd",
"listing": {
"$type": "com.atproto.repo.strongRef",
"uri": "at://did:plc:creator/money.atmosphere.product/abc",
"cid": "bafy..."
},
"payerDid": "did:plc:buyer",
"returnUrl": "https://app.example/checkout/return",
"cancelUrl": "https://app.example/product/abc",
"metadata": {
"appOrderId": "ord_123"
},
"publicRecords": {
"appRecord": "private",
"attestation": "private"
}
}Payment plan
Before ATM creates a processor session, it resolves one private payment plan. The plan freezes the routing mode, tax snapshot, payment-method policy, reporting category, fee split, public-record policy, and future allocation/settlement legs for that checkout.
| Routing | Direct charge is the launch default. Marketplace-style destination charges or separate transfers are future feature-flagged modes. |
|---|---|
| Payment methods | App/environment policy applies before Stripe eligibility. Klarna is excluded by default. |
| Tax | ATM stores tax behavior, tax code/category, liability, and invoice issuer snapshots privately. |
| Allocations | Gross recipient amount, app fee pool, app share, and ATM share are modeled separately for future split settlement. |
| Portal boundary | Stripe Customer Portal is only used for safe payment method update flows; amount changes, cancellations, and entitlements stay in ATM. |
Public records
App dashboard defaults are frozen onto each payment when checkout starts. A checkout can override those defaults withpublicRecords. Whenattestation resolves toprivate, the payment can still complete and trigger app fulfillment events, but ATM does not write publicnetwork.attested.* records for it.
Completion
After payment, ATM sends app events and updates status. Apps should use redirects for UI continuity, not fulfillment truth.
- Use
payment.completedfor fulfillment. - Use status polling when the buyer returns before the webhook arrives.
- Use idempotency keys around app order creation and fulfillment.
- Do not assume a browser return means a payment settled.