> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://mpp.dev/api/mcp` to find what you need.

# Tempo subscription \[Recurring billing]

## Choose a signing account

### Direct

Create a server-only wallet.ts module, then import account wherever an example creates a local signing account.

```ts
import { privateKeyToAccount } from 'viem/accounts'

export const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
```

### Privy

Create an EVM wallet in Privy, fund it with the required currency on this page's network, and keep PRIVY\_APP\_SECRET server-side.

```bash
pnpm add @privy-io/node
```

Create a server-only privy.ts module, then import its account wherever an example configures account or feePayer.

```ts
import { PrivyClient } from '@privy-io/node'
import { createViemAccount } from '@privy-io/node/viem'

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!,
})

export const account = createViemAccount(privy, {
  address: process.env.PRIVY_WALLET_ADDRESS as `0x${string}`,
  walletId: process.env.PRIVY_WALLET_ID!,
})
```

createViemAccount delegates signatures to the Privy wallet, so it replaces any local viem account in the examples on this page.

The `subscription` intent enables recurring stablecoin payments on Tempo with reusable access authorization.

Use subscriptions when access has a fixed price per billing period: paid plans, premium API tiers, recurring MCP tool access, and usage bundles that renew on a schedule.

## Why subscriptions matter

Charges work well for one-time purchases. Sessions work well when usage changes inside a request. Subscriptions cover the third common pattern: the client authorizes recurring access once, and the server bills each period without asking the client to sign every request.

A Tempo subscription uses a key authorization. The client authorizes a scoped access key to transfer a fixed amount of a specific TIP-20 token to a specific recipient once per period until `subscriptionExpires`. The server stores the active subscription record and returns Receipts on later requests without another Credential while the current period is paid.

## Choosing a payment method

| | **Charge** | **Session** | **Subscription** New |
|---|---|---|---|
| **Pattern** | One-time payment | Pay-as-you-go usage | Recurring access |
| **Client action** | Sign each paid transfer | Open channel and sign vouchers | Authorize an access key once |
| **Server hot path** | Verify and broadcast transfer | Verify voucher signatures | Resolve active subscription |
| **Best for** | Single API calls and purchases | LLM tokens, bytes, streamed usage | Plans, recurring API access, memberships |
| **Renewal** | None | Top up channel as needed | Bill each day or week |

## Flow

```mermaid
sequenceDiagram
  participant Client
  participant Server
  participant Tempo
  Client->>Server: Protected request
  Server-->>Client: 402 subscription Challenge
  Note over Client: Authorize access key
  Client->>Server: Retry with Credential
  Server->>Tempo: Transfer first period
  Tempo-->>Server: Transaction hash
  Server-->>Client: 200 OK + Receipt
  Client->>Server: Later request
  Server-->>Client: 200 OK + Receipt

```

## Activation

The first request activates the subscription. The server resolves the request to a stable lookup key, such as `user:123:plan:pro`, and includes an access key in the Challenge. The client signs a `keyAuthorization` Credential that binds:

* `amount`
* `currency`
* `periodCount`
* `periodUnit`
* `recipient`
* `subscriptionExpires`
* `accessKey`

The server verifies the Credential, charges the first period, stores a `SubscriptionRecord`, and returns a Receipt with a `subscriptionId`.

## Access reuse

After activation, future requests can reuse the stored subscription while it is active and current. With `requireCredential`, each request proves the same payer signed the request before the server looks up the subscription. The server calls `resolve`, finds the subscription record for the route or payer, validates that it still matches the request terms, and returns a Receipt.

```mermaid
sequenceDiagram
  participant Client
  participant Server
  participant Store
  Client->>Server: Request protected resource
  Server->>Store: Lookup active subscription by resolved key
  Store-->>Server: SubscriptionRecord
  Server->>Server: Check expiry, request binding, paid period
  Server-->>Client: 200 OK + Receipt

```

## Renewals

When the next billing period starts, the server renews the subscription before granting access. The SDK uses an atomic store lock so concurrent requests do not charge the same period twice. If one request is already renewing, another request receives `409` with `Retry-After: 1`.

```mermaid
sequenceDiagram
  participant RequestA
  participant RequestB
  participant Store
  participant Tempo
  RequestA->>Store: Lock renewal period
  RequestB->>Store: Try same renewal
  Store-->>RequestB: In flight
  RequestA->>Tempo: Transfer period payment
  Tempo-->>RequestA: Transaction hash
  RequestA->>Store: Commit renewed record
  RequestA-->>RequestA: 200 OK + Receipt
  RequestB-->>RequestB: 409 Retry-After

```

You can renew in the request path with `renew`, or run renewals from a background worker with [`tempo.renewSubscription`](/sdk/typescript/server/Method.tempo.renewSubscription).

## Cancellation

Cancel a Tempo subscription by marking its stored `SubscriptionRecord` with `canceledAt`. `mppx` treats records with `canceledAt` or `revokedAt` as inactive, so later protected requests return a new `402` Challenge instead of reusing or renewing the old subscription.

The recommended client flow is to call your cancellation endpoint first, then optionally revoke the Tempo access key as a backstop. Server cancellation controls product access. Access-key revocation blocks future on-chain renewal attempts, but it doesn't update the merchant's stored subscription record by itself.

```ts twoslash
import { Store } from 'mppx/server'
import { Subscription } from 'mppx/tempo'

const store = Store.memory()
const subscriptions = Subscription.fromStore(store)

export async function cancelSubscription(userId: string) {
  const subscription = await subscriptions.getByKey(`user:${userId}:plan:pro`)
  if (!subscription) return false

  await subscriptions.put({
    ...subscription,
    canceledAt: new Date().toISOString(),
  })

  return true
}
```

Keep the canceled record for audit and reconciliation. If the client subscribes again, activation creates a new `subscriptionId` for the same resolved lookup key.

On Tempo, clients that know the authorized access key can revoke it from the payer account:

```ts twoslash
import { createClient, http } from 'viem'
import { tempo } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { Actions } from 'viem/tempo'

const client = createClient({
  account: privateKeyToAccount(
    '0x0000000000000000000000000000000000000000000000000000000000000001', // your account
  ),
  chain: tempo,
  transport: http(),
})

await Actions.accessKey.revokeSync(client, {
  accessKey: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
})
```

## Receipts

Subscription Receipts confirm activation or renewal. The `reference` field is the Tempo transaction hash for the period payment.

| Field | Description |
|---|---|
| `externalId` | Optional app-defined reference |
| `method` | Always `"tempo"` |
| `reference` | Tempo transaction hash |
| `status` | Always `"success"` |
| `subscriptionId` | Server-issued subscription identifier |
| `timestamp` | Receipt timestamp |

## Integration

### Server

Register `tempo.subscription()` explicitly. The `tempo.common()` helper registers charge and session intents, but it doesn't register subscriptions.

```ts twoslash
import { Mppx, Store, tempo } from 'mppx/server'

const store = Store.memory()

const mppx = Mppx.create({
  methods: [
    tempo.subscription({
      amount: '1.00',
      currency: '0x20c0000000000000000000000000000000000000',
      periodCount: '1',
      periodUnit: 'week',
      recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
      requireCredential: true,
      resolve: async ({ source }) => {
        if (!source) return null
        return { key: `payer:${source.chainId}:${source.address}:plan:pro` }
      },
      store,
      subscriptionExpires: new Date('2027-01-01T00:00:00.000Z'),
    }),
  ],
})

export async function handler(request: Request) {
  const result = await mppx.tempo.subscription({})(request)

  if (result.status === 402) return result.challenge

  const response = result.withReceipt(Response.json({ plan: 'pro' }))
  console.log(response.status)
  // @log: 200
  return response
}
```

:::warning
Use a durable atomic store such as Redis, Upstash, or Cloudflare KV for production. `Store.memory()` is for local development.
:::

### Client

Register `tempo.subscription()` on the client. The SDK signs the access-key authorization and retries the request after the server returns a subscription Challenge.

#### Accounts SDK

```ts twoslash
import { Mppx, tempo } from 'mppx/client'
import { Provider } from 'accounts'

const provider = Provider.create({ mpp: false }) // Avoid double 402 handling; mppx is configured below.
await provider.request({ method: 'wallet_connect' })

Mppx.create({
  methods: [tempo.subscription({
      account: provider.getAccount({ signable: true }),
      getClient: provider.getClient,
    })],
})

const response = await fetch('https://api.example.com/pro')
console.log(response.status)
// @log: 200
```

#### viem

```ts twoslash
import { Mppx, tempo } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount('0xabc…123')

Mppx.create({
  methods: [tempo.subscription({ account })],
})

const response = await fetch('https://api.example.com/pro')
console.log(response.status)
// @log: 200
```

## Advanced options

### Custom activation

Pass `activate` when your application owns settlement and record creation. The SDK still verifies the `keyAuthorization` Credential and validates the returned Receipt and subscription record.

### Custom access keys

Pass `accessKey` or return `accessKey` from `resolve` when you want to use an existing access key. Omit it for the recommended path: the server generates and stores one access key per resolved subscription key.

### Background renewal

Use [`tempo.renewSubscription`](/sdk/typescript/server/Method.tempo.renewSubscription) from a cron job when you want billing to happen before the next user request.

## Related

[Build a subscription-gated API](/guides/subscription-payments) — Add recurring access to an API route

[Subscription intent](/intents/subscription) — Understand the method-agnostic recurring payment intent

[Server API reference](/sdk/typescript/server/Method.tempo.subscription) — Configure activation, reuse, and renewal

[Client API reference](/sdk/typescript/client/Method.tempo.subscription) — Sign subscription key authorizations
