Docs

App Node SDK

The closed-beta TypeScript package for calling ATM from app backends.

Closed beta@atmosphere-money/app-nodeSDK beta: 0.0.0-beta.0ATM API beta: 2026-0642 lexicons

Compatible with the closed-beta ATM app APIs and versioned ATM event headers. Check atm-api-version on every webhook or XRPC receiver event.

Package status

ATM's first SDK surface is the App Node package in packages/app-node. It is shaped as @atmosphere-money/app-node and is published on npm for invited beta app developers. The exported API is the intended app-server package surface.

Published beta packageServer-side onlyInstall with @beta

SDK beta and API compatibility

The beta SDK version is 0.0.0-beta.0. It targets ATM API beta: 2026-06 and the current closed-beta app dashboard contracts. Webhook and XRPC receiver events include an atm-api-version header; receivers should log it and avoid accepting unknown future shapes without an explicit compatibility review.

Package@atmosphere-money/app-node
SDK version0.0.0-beta.0
API compatibilityATM API beta: 2026-06
Event version signalatm-api-version header on HTTP webhooks and XRPC receiver events.
Breaking changesDocumented in SDK reference, Versioning, and package CHANGELOG before broad app onboarding.

Install

During beta, install the package directly from npm. Keep it on your backend and pin beta updates deliberately.

sh
npm install @atmosphere-money/app-node@beta

The package requires Node 22 or newer and exports ESM plus TypeScript declarations from dist.

Initialize

The SDK asks your backend for a fresh service-auth JWT for every app-facing XRPC call. Scope each JWT to the exact method via lxm and to the ATM broker audience.

ts
import { createAtmAppClient } from "@atmosphere-money/app-node";

const atm = createAtmAppClient({
  getServiceAuthToken: async ({ lxm, aud }) => {
    return mintMyAppServiceAuthJwt({
      lxm,
      aud,
      exp: Math.floor(Date.now() / 1000) + 60,
      jti: crypto.randomUUID()
    });
  }
});

Start checkout

Check recipient payability before showing checkout buttons, then initiate the strict attested.network broker route with ATM's private checkout envelope.

ts
const payout = await atm.getPayoutStatus("did:plc:creator");
if (!payout.payable) {
  return { disabled: true, reason: payout.reason };
}

const checkout = await atm.initiatePayment({
  recipient: "did:plc:creator",
  amount: 1200,
  currency: "usd",
  paymentType: "shop",
  environment: "test",
  returnUrl: "https://app.example/orders/ord_123",
  cancelUrl: "https://app.example/products/product_123",
  metadata: {
    appOrderId: "ord_123"
  }
});

return checkout.url;

Verify webhooks

Use the exact raw body and ATM headers. Deduplicate by delivery id before writing fulfillment side effects.

ts
import {
  constructAtmWebhookEvent,
  constructTypedAtmWebhookEvent,
  createHonoWebhookHandler,
  createCloudflareWorkerWebhookHandler
} from "@atmosphere-money/app-node";

const event = constructAtmWebhookEvent({
  rawBody,
  secret: process.env.ATM_WEBHOOK_SECRET!,
  headers: {
    signature: request.headers.get("atm-signature"),
    deliveryId: request.headers.get("atm-delivery-id"),
    event: request.headers.get("atm-event"),
    apiVersion: request.headers.get("atm-api-version"),
    environment: request.headers.get("atm-environment")
  }
});

if (await hasHandled(event.id)) {
  return new Response("ok");
}

switch (event.type) {
  case "payment.completed":
    await fulfillPayment(event.data.payment);
    break;
  case "tickets.issued":
    await showTicketsToBuyer(event.data.tickets);
    break;
}

await markHandled(event.id);

When you know the expected event type, the typed constructor gives you a narrower event.data shape.

ts
const paymentEvent = constructTypedAtmWebhookEvent({
  rawBody,
  secret: process.env.ATM_WEBHOOK_SECRET!,
  expectedType: "payment.completed",
  headers: {
    signature: request.headers.get("atm-signature"),
    deliveryId: request.headers.get("atm-delivery-id"),
    apiVersion: request.headers.get("atm-api-version"),
    environment: request.headers.get("atm-environment")
  }
});

await fulfillPayment(paymentEvent.data.payment);

For route-level integration, use createNodeWebhookHandler, createNextWebhookRoute, createExpressWebhookHandler, createHonoWebhookHandler, or createCloudflareWorkerWebhookHandler. All of them verify the signed raw body and give your code a typed ATM event before fulfillment.

Tickets helpers

Tickets uses the same App Node package because ticket holds, free limited claims, QR/pass checks, and checkout all depend on ATM app auth and payment state.

ts
const availability = await atm.getTicketAvailability({
  environment: "test",
  eventUri: "at://did:plc:organizer/community.lexicon.calendar.event/demo"
});

const hold = await atm.createTicketHold(createTicketHoldBody({
  environment: "test",
  eventUri: "at://did:plc:organizer/community.lexicon.calendar.event/demo",
  buyerDid: "did:plc:buyer",
  buyerAssertionJwt: "short-lived-buyer-assertion",
  items: [{ ticketTierId: "tier_123", quantity: 2 }],
  returnUrl: "https://app.example/tickets/return",
  cancelUrl: "https://app.example/events/demo",
  idempotencyKey: "hold:event:buyer:tier_123:2"
}));

const freeClaim = await atm.claimFreeTicket(createFreeTicketClaimBody({
  environment: "test",
  eventUri: "at://did:plc:organizer/community.lexicon.calendar.event/demo",
  ticketTierId: "tier_free",
  buyerDid: "did:plc:buyer",
  buyerAssertionJwt: "short-lived-buyer-assertion",
  idempotencyKey: "claim:event:buyer:tier_free"
}));

const verified = await atm.verifyTicket({
  environment: "test",
  ticketToken: "opaque_scan_token"
});

Ticket-specific concepts, diagrams, and scanner flows live on atmosphere.tickets. ATM docs cover the shared payment, app auth, and event surfaces.

Framework examples

Copyable examples live in the repo so app developers can start from their own backend style without rewriting the verification logic.

Next checkout routedocs/developer/examples/next-checkout-route.ts
Next webhook routedocs/developer/examples/next-webhook-route.ts
Next XRPC receiver routedocs/developer/examples/next-xrpc-receiver-route.ts
Express webhook routedocs/developer/examples/express-webhook-route.ts
Fastify webhook routedocs/developer/examples/fastify-webhook-route.ts
Hono webhook routedocs/developer/examples/hono-webhook-route.ts
Cloudflare Worker webhookdocs/developer/examples/cloudflare-worker-webhook.ts
Happy path appdocs/developer/examples/happy-path-app.ts
Ticket hold checkoutdocs/developer/examples/ticket-hold-checkout.ts
Free limited ticket claimdocs/developer/examples/free-ticket-claim.ts

Errors and retries

Failed requests throw AtmApiError with a stable code, HTTP status, and parsed response body. Retry transport failures and 5xx responses with the same app idempotency key. Do not retry a payment by asking the buyer to pay again unless ATM says the checkout is failed or expired.

ts
import { AtmApiError } from "@atmosphere-money/app-node";

try {
  return await atm.initiatePayment(input);
} catch (error) {
  if (error instanceof AtmApiError) {
    if (error.status >= 500) retryWithSameIdempotencyKey();
    if (error.code === "RecipientNotPayable") showSetupRequiredState();
  }
  throw error;
}

API surface

createAtmAppClientCreate a backend client for payout status, checkout, payment status, profile, and ticket calls.
ATM_XRPC_METHODSStable method constants used by the SDK so app code can avoid scattered NSID strings.
createAtmCheckoutProductBuild ATM's private `atm.checkout.v1:` product envelope for strict attested.network initiate calls.
constructAtmWebhookEventVerify HTTP webhook signature and parse the event envelope.
constructTypedAtmWebhookEventVerify a webhook and narrow data for a known event type.
createNodeWebhookHandlerCreate a Request/Response webhook handler for Node-compatible runtimes.
createNextWebhookRouteAlias for Next route handlers and other Web Request-compatible runtimes.
createExpressWebhookHandlerCreate an Express-style webhook handler without adding an Express dependency.
createHonoWebhookHandlerCreate a Hono route handler around the same signed webhook verification.
createCloudflareWorkerWebhookHandlerCreate a Workers fetch handler around the same signed webhook verification.
createTicketHoldBodyValidate and prune paid ticket hold request bodies before calling the Tickets XRPC method.
createFreeTicketClaimBodyValidate and prune free limited-ticket claim request bodies before calling the Tickets XRPC method.
constructAtmXrpcReceiverEventVerify optional XRPC receiver service-auth and parse the same event envelope.
constructTypedAtmXrpcReceiverEventVerify an XRPC receiver event and narrow data for a known event type.
verifyServiceAuthRequestVerify a service-auth bearer request before custom XRPC receiver routing.
signAtmWebhookPayloadTest helper for local webhook signature fixtures.
AtmApiErrorStructured request failure with code, status, and parsed body.

Payments

getPayoutStatus, initiatePayment, getPaymentStatus.

Profiles

getProfile reads ATM profile data from the AppView.

Tickets

create/update/archive tiers, capacity groups, availability, holds, release, claims, lists, verify, and check-in.

Events

HTTP webhook and optional XRPC receiver constructors use the same ATM envelope shape.

Go live with the SDK

  • Create the app DID and register the app role in ATM.
  • Keep service-auth minting and webhook secrets on the app backend.
  • Configure separate test and live webhook URLs or XRPC receivers.
  • Use an app idempotency key for each app order, ticket hold, or free claim.
  • Verify webhook signatures or XRPC receiver service-auth before fulfillment.
  • Store delivery ids and payment ids before sending tickets, downloads, emails, or app mutations.
  • Test payability-disabled states, refunds, subscription changes, ticket release, and redrive.
  • Switch the app dashboard from test to live intentionally; do not reuse test secrets in live.

Browser and Supper packages

Browser embeds should be separate packages. ATM checkout embeds can become an @atmosphere-money/checkout-embed wrapper around an ATM-hosted iframe, with hosted checkout as fallback.

Creators who only want a button on their own website should use Supper's add-to-your-site guide. Supper remains the app in that flow; creators do not need app service-auth, webhooks, SDKs, or app onboarding.

Ergonomics v0.1

Keep the beta package small until the first external app developers have used it. The SDK should remove the sharp edges testers hit in real integrations, not become a large platform wrapper before those needs are proven.

  • Add helpers only when a tester or reference app needs them in real integration work.
  • Keep low-level primitives available beside route helpers.
  • Do not put browser checkout, Supper widgets, or Stripe client secrets in app-node.
  • Prefer framework examples over framework dependencies inside the core package.
  • Graduate repeated docs snippets into SDK helpers only after they appear in more than one real app.
  • Treat webhook/XRPC verification, idempotency, checkout initiation, status checks, and Tickets calls as the core v0.1 surface.

Publish readiness

  • Keep the @atmosphere-money npm org restricted to release maintainers.
  • Keep package exports server-only and free of browser checkout secrets.
  • Run npm run check and npm run release:check in packages/app-node before tagging a version.
  • Run npm run check in packages/testing before publishing fixture helpers.
  • Run npm run publish:check before publishing each beta; the package license is MIT.
  • Run npm pack --dry-run and inspect the tarball contents.
  • Publish generated TypeScript declarations with every release.
  • Keep starter apps importing packed SDK tarballs in CI rather than relying on monorepo source imports.
  • Document breaking changes in SDK docs before broad app onboarding.
App Node SDK - Atmosphere Money Docs