First real app tutorial
The canonical app integration route: register, configure test mode, request creator approval, create checkout, receive payment.completed, and fulfill.
Compatible with the closed-beta ATM app APIs and versioned ATM event headers. Check atm-api-version on every webhook or XRPC receiver event.
Goal
This is the canonical ATM app integration path. At the end, the app has a test environment, a receiver, a successful checkout path, and a redrive-safe event handler that fulfills only from ATM truth.
- 01
Register app
Add the app role to the app DID and keep configuration in test mode.
- 02
Configure test
Enable modules, add a receiver, copy the test secret, and subscribe to events.
- 03
Request approval
Ask the creator to approve this app, environment, fee cap, payment types, and public-record behavior.
- 04
Create checkout
Create an app order, build the private ATM checkout envelope, and redirect the buyer.
- 05
Receive event
Verify payment.completed, deduplicate the delivery id, and store the ATM payment id.
- 06
Fulfill
Mark the app order paid and move toward live only after redrive and refund paths pass.
App server
Runs the starter kit or your own equivalent server-side integration.
ATM dashboard
Stores app config, modules, webhooks, delivery logs, and test/live settings.
ATM checkout
Owns the buyer payment form, wallets, receipts, status, and proof coordination.
App fulfillment
Runs only after a verified ATM event or confirmed ATM status.
Prerequisites
- An allowlisted app DID with access to app registration.
- A server runtime that can keep app secrets private.
- Optionally, a tunnel or deployed URL if you want signed webhook wake-ups.
- A test recipient that has completed payment setup when testing paid checkout.
- A plan for idempotency keys and delivery id deduplication.
1. Register the app
- 01
Sign in
Use the DID that represents the app, not an individual developer's personal account.
- 02
Request app role
ATM adds the app role while preserving payer or creator roles on the same DID.
- 03
Complete profile
Name, avatar, description, and app URL become the app-facing identity in ATM.
- 04
Stay in test
The app dashboard defaults to test configuration for modules, secrets, and receivers.
2. Configure test mode
| Modules | Enable only what the app is testing: payments, products, subscriptions, tickets, or callbacks. |
|---|---|
| Event delivery | Use the default canonical XRPC receiver plus authenticated status polling, or opt into signed HTTP webhooks for conventional apps. |
| Signing secret | Needed only after opting into HTTP webhooks; store it in server-side env. |
| Event selection | Subscribe to only the events your app knows how to handle. |
| App fee | Set the app fee policy intentionally before any checkout test. |
3. Run the starter kit
The starter kit proves local receiver verification and status handling before you wire application-specific order logic.
cd examples/atm-node-app
cp .env.example .env
npm run build --prefix ../../packages/app-node
npm install
npm run typecheck
npm run smoke
npm run dev
curl http://localhost:8787/healthThe compact source file at docs/developer/examples/happy-path-app.tsshows the same app-order, checkout, webhook, and fulfillment sequence without the full starter-kit HTTP server.
4. Request creator approval
Before an app can accept payments for a creator, the creator approves that app in ATM. Approval is scoped to app DID, recipient DID, environment, payment types, app fee cap, and public-record behavior.
const approval = await atm.requestRecipientApproval({
recipientDid,
environment: "test",
paymentTypes: ["shop"],
feeShareBps: 300,
publicRecords: {
enabled: true,
defaults: {
attestation: "public"
}
},
requestReason: "Enable Example App checkout",
setupReturnUrl: "https://app.example/settings/payments"
});
if (approval.status !== "approved") {
return Response.redirect(approval.dashboardUrl);
}Fee decreases stay within scope. New payment types, fee increases, or changes that make records more public move the approval to needs-review until the creator confirms.
5. Create the first checkout
For a real app, create an app order first, then create the ATM checkout envelope from that order. The browser should receive only the returned ATM checkout URL.
const order = await createAppOrder({
recipientDid,
amountCents: 1200,
currency: "usd"
});
const checkout = await atm.initiatePayment({
environment: "test",
recipient: order.recipientDid,
amount: order.amountCents,
currency: order.currency,
paymentType: "shop",
metadata: { appOrderId: order.id },
returnUrl: "https://app.example/orders/" + order.id,
cancelUrl: "https://app.example/support"
});
await orders.saveAtmStatusToken(order.id, checkout.token);
return Response.redirect(checkout.url);6. Verify status reconciliation
Complete a test checkout and verify that a backend worker can load the stored initiation token, poll canonical status, and apply the terminal result idempotently. If you enable push, also verify that a signed event only wakes the same reconciliation path.
- The status token is stored server-side before the browser redirect.
- The worker polls with app service-auth and never uses the browser checkout token.
- Reprocessing the same completed status does not fulfill twice.
- Optional receivers validate signature/service-auth and wake the poller without directly fulfilling.
7. Inspect dashboard state
| App dashboard | Check module status, test/live environment, and fee settings. |
|---|---|
| Webhooks | Confirm delivery status, attempts, response code, and redrive action. |
| Payments | Confirm the payment row, app id, app fee fields, customer fields, and proof status. |
| Products or tickets | Confirm linked fulfillment refs or issued ticket state if those modules are enabled. |
8. Ready for live
- Guest and signed-in payer checkout paths pass.
- The configured canonical XRPC receiver or compatibility webhook path passes.
- Failed delivery and redrive pass.
- Refund/cancel/subscription-change paths pass for enabled modules.
- Private data stays off protocol and out of public logs.
- Live secrets are separate from test secrets.