AIOT Payment x402 is in public beta. Install the skill for your AI agent.
GuidesFor DevelopersSession Stores

Session Stores

Checkout state lives in a SessionStore. The SDK ships an InMemorySessionStore suitable for development and single-process deployments. For horizontally-scaled production workloads, implement the three-method SessionStore interface against Redis, Postgres, or whatever durable store you already operate.

The SessionStore interface

export interface SessionStore {
  get(id: string): Promise<CheckoutSession | undefined>
  set(id: string, session: CheckoutSession): Promise<void>
  delete(id: string): Promise<void>
}

That’s the whole contract. CheckoutSessionManager layers TTL enforcement, status transitions, and atomic updates on top.

NameTypeDescription
get*(id) => Promise<CheckoutSession | undefined>Fetch by id. Return undefined if missing.
set*(id, session) => Promise<void>Upsert by id. Overwrite on conflict.
delete*(id) => Promise<void>Remove by id. No-op if missing.

Default: InMemorySessionStore

import { AIOTShopifyClient, InMemorySessionStore } from '@aiot/shopify-x402'
 
const client = new AIOTShopifyClient({
  // ...,
  sessionStore: new InMemorySessionStore()
})

A simple Map<string, CheckoutSession>. Zero dependencies, zero setup. Do not use this in a horizontally scaled fleet — a session created on pod A can’t be completed by pod B.

Redis example

import { createClient } from 'redis'
import type { SessionStore, CheckoutSession } from '@aiot/shopify-x402'
 
const redis = createClient({ url: process.env.REDIS_URL })
await redis.connect()
 
const SESSION_TTL_SECONDS = 30 * 60 // matches SDK's 30-minute TTL
 
export const redisStore: SessionStore = {
  async get(id) {
    const raw = await redis.get(`aiot:session:${id}`)
    return raw ? (JSON.parse(raw) as CheckoutSession) : undefined
  },
  async set(id, session) {
    await redis.set(
      `aiot:session:${id}`,
      JSON.stringify(session),
      { EX: SESSION_TTL_SECONDS }
    )
  },
  async delete(id) {
    await redis.del(`aiot:session:${id}`)
  }
}
 
const client = new AIOTShopifyClient({
  // ...,
  sessionStore: redisStore
})
Tip

Let Redis handle TTL via the EX option. The SDK still transitions sessions to expired in-memory when get is called on an old session, but letting Redis evict the row keeps your key-count bounded.

Sessions are mutated in place

CheckoutSessionManager reads a session, mutates a field, and writes it back. Between read and write there is no lock — the SDK assumes the happy path of a single user completing a single checkout is not racy. If you’re building a multi-writer system (e.g. a background worker that settles in parallel with a polling UI), consider wrapping set in a version check or using WATCH/MULTI in Redis.

What’s in a session?

CheckoutSession is defined in src/checkout/session.ts. The key fields:

NameTypeDescription
idstring128-bit hex id generated at create time.
status'pending' | 'signing' | 'settling' | 'completing' | 'completed' | 'failed' | 'expired'Current lifecycle state.
cartItemsCartItem[]Line items from createCheckout.
fiatTotalstringFiat total in the request currency.
usdcAmountstringUSDC base-unit amount (6 decimals).
shopifyDraftOrderIdstringShopify draft order GID.
x402OrderIdstringOrder id returned by the facilitator.
expiresAtnumberUnix millisecond expiry.
paymentResultPaymentResult | undefinedSet on completion.