> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chipipay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# x402 + Session Keys

> Automated x402 payments using SNIP-9 session keys

## The Killer Combo

x402 payments are USDC transfers. Session keys on CHIPI wallets authorize transfers without requiring the owner key each time.

| Mode | User Experience |
| - | - |
| **Without session** | Each payment requires signing (wallet popup / biometrics / PIN) |
| **With session** | Payments happen automatically — zero interaction per request |

This makes x402 + sessions ideal for:

* AI agent autonomous API consumption
* Streaming data feeds (pay-per-query)
* Backend automation (server-to-server payments)
* Mobile apps with "subscribe for X hours" UX

## React Implementation

```typescript theme={null}
import { useChipiSession, useX402Payment } from "@chipi-stack/nextjs";

function AutoPayContent() {
  // Step 1: Set up session
  const {
    session,
    hasActiveSession,
    createSession,
    registerSession,
  } = useChipiSession({
    wallet,
    encryptKey: pin,
    getBearerToken: getToken,
  });

  // Step 2: Pass session to x402
  const { x402Fetch, isPaying, paymentCount } = useX402Payment({
    wallet,
    encryptKey: pin,
    getBearerToken: getToken,
    session,  // Session key signs payments automatically
    maxPaymentAmount: "0.50",
  });

  // Step 3: Set up session (one-time)
  const setupSession = async () => {
    await createSession();
    await registerSession({
      allowedEntrypoints: ["transfer"],  // Only `transfer` calls (on any token)
    });
  };

  // Step 4: Use — payments are fully automatic
  const fetchData = async () => {
    // No popup, no PIN, no interaction
    const response = await x402Fetch("https://api.example.com/ai-query?q=...");
    return response.json();
  };

  return (
    <div>
      {!hasActiveSession ? (
        <button onClick={setupSession}>Enable Auto-Pay (6 hours)</button>
      ) : (
        <div>
          <button onClick={fetchData} disabled={isPaying}>
            Query AI API
          </button>
          <p>{paymentCount} queries paid</p>
        </div>
      )}
    </div>
  );
}
```

## How It Works Under the Hood

1. **Session creation**: Generate a temporary keypair, encrypt private key with user's PIN
2. **Session registration**: Register the session public key on the wallet contract (one owner signature)
3. **Session constraint**: `allowedEntrypoints: ["transfer"]` restricts the session to `transfer` calls. The whitelist is not tied to a contract, so it allows `transfer` on every token the wallet holds. Cap amounts per token with a [spending policy](/sdk/guides/spending-policies). Function names need 14.13.1 or later; on earlier versions pass the selector (`hash.getSelectorFromName("transfer")`)
4. **x402 payment**: When a 402 is received, the hook uses `executeTransactionWithSession()` instead of `executeTransaction()` — the session key signs automatically

```
User sets up session (one-time PIN entry)
         |
         v
[Session key registered on wallet contract]
         |
         v
x402Fetch("https://api.example.com/data")
         |
         v
Server returns 402 → parse requirement → sign with session key → retry
         |                                     ^
         |                                     |
         +-- No user interaction! Session key signs automatically
```

## Node.js Backend Automation

For server-side automation where you want to consume paid APIs:

```typescript theme={null}
import { X402Client, DirectSigner, createAccount } from "@chipi-stack/core";
import { ChipiServerSDK } from "@chipi-stack/backend";

const sdk = new ChipiServerSDK({
  apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
  apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});

// Create session for automated payments
const session = sdk.sessions.createSessionKey({
  encryptKey: process.env.ENCRYPT_KEY!,
  durationSeconds: 3600, // 1 hour
});

// Register session with transfer-only permissions
await sdk.sessions.addSessionKeyToContract({
  encryptKey: process.env.ENCRYPT_KEY!,
  wallet: myWallet,
  sessionConfig: {
    sessionPublicKey: session.publicKey,
    validUntil: session.validUntil,
    maxCalls: 100,
    allowedEntrypoints: ["transfer"],
  },
}, bearerToken);

// Now use session for automated x402 payments
const signer = new DirectSigner(decryptedSessionKey);
const account = await createAccount({ signer, provider });
const client = new X402Client(account, { maxPaymentAmount: "0.10" });

// Automated — no human interaction needed
for (const query of queries) {
  const response = await client.fetch(`https://api.example.com/ai?q=${query}`);
  const result = await response.json();
  // Process result...
}
```

## Verify a payment the agent sent itself (14.14.0+)

When the paying agent sends the USDC transfer with its own session key (`executeTransactionWithSession`) and gives you the transaction hash, no facilitator sits in between. A hash is not a payment: check the receipt with `SessionTxVerifier` before serving the request.

```typescript theme={null}
import { SessionTxVerifier } from "@chipi-stack/backend";

const verifier = new SessionTxVerifier({ txHashStore }); // unique-index store in production

const result = await verifier.verifyTransfer({
  transactionHash,
  token: USDC,
  from: agentWallet,
  to: TREASURY,
  minAmount: 1_000n, // $0.001 in USDC base units
});
if (!result.valid) {
  return new Response(result.reason, { status: 402 });
}
```

Take `agentWallet` from the caller's authenticated identity, not the request: hashes are public, so an unauthenticated `from` lets anyone who sees the transaction claim it first. It accepts the payment only if the transaction succeeded, a `Transfer` event from `token` moved at least `minAmount` from `from` to `to`, and the hash was never accepted before. Failures come back as `REVERTED`, `TRANSFER_NOT_FOUND`, `AMOUNT_TOO_LOW` or `ALREADY_USED`; a check that fails does not consume the hash. It throws only when the receipt cannot be read in time, so retry those. `X402Facilitator` is unchanged. See [Agent with a session key and a passkey](/sdk/guides/agent-sessions) for the full flow.

## Pay for Chipi's AI API from the session (14.15.0+)

Chipi's own `POST /v1/ai/think` sells decisions over x402 when your credits are short. `sdk.ai.think(params, { payWithSession })` is the buyer side of the pattern above, done for you: it pays the 402 offer with a session-signed USDC transfer, checks the receipt with `SessionTxVerifier` before sending `X-PAYMENT`, and refuses before paying when the price is above your `maxAmount`. The paying wallet must belong to the same organization as the `sk_`; from 14.15.1 the SDK checks that with the API before paying (`X402_PAYER_NOT_IN_ORG`). See [DeFi Intelligence](/services/ai-api/defi-intelligence#pay-per-call-with-x402).

## On-Chain Spending Limits

Session keys alone don't enforce dollar limits — `maxCalls` caps the number of transactions, not the amount. For real budget control, use **Spending Policies**:

```typescript theme={null}
// After registering the session, set on-chain limits
await setSpendingPolicy({
  token: USDC_ADDRESS,
  maxPerCall: 100_000n,        // Max $0.10 per x402 payment (6 decimals)
  maxPerWindow: 50_000_000n,   // Max $50 per day
  windowSeconds: 86400,        // 24h rolling window
});
```

The contract enforces this automatically: a payment over the per-call limit or the rolling window total is refused by the wallet. Sent through Chipi's paymaster, the receipt can still say `success: true` with nothing moved, so the seller (and a careful buyer) checks for the `Transfer`, which is what `SessionTxVerifier` and `sdk.ai.think`'s `payWithSession` do.

> [!NOTE]
>
> Spending policies require **CHIPI v33** wallets. See the full guide: [Spending Policies](/sdk/guides/spending-policies).

### Three Layers of Protection

| Layer | Scope | Enforced By |
| - | - | - |
| `maxPaymentAmount` (X402Client) | Per-request, client-side | SDK (can be bypassed) |
| `maxCalls` (Session) | Total transaction count | Smart contract |
| **Spending Policy** | Per-call amount + rolling window total | Smart contract (cannot be bypassed) |

For production x402 deployments, use all three.

## Security Considerations

### Session Scope

Always restrict session keys to the minimum required permissions:

```typescript theme={null}
await registerSession({
  allowedEntrypoints: ["transfer"],  // Only `transfer` calls (on any token)
  maxCalls: 100,                     // Limit total calls
});

// Then add on-chain spending limits
await setSpendingPolicy({
  token: USDC_ADDRESS,
  maxPerCall: 100_000n,           // $0.10 per call
  maxPerWindow: 10_000_000n,      // $10 total per session window
  windowSeconds: 21600,           // 6h (match session duration)
});
```

This creates a comprehensive spending cap:

* **Per-request limit**: `maxPerCall` enforced on-chain (\$0.10 per API call)
* **Session window limit**: `maxPerWindow` enforced on-chain (\$10 per 6 hours)
* **Transaction count limit**: `maxCalls: 100` enforced on-chain
* **Client-side guard**: `maxPaymentAmount: "0.10"` (early rejection, saves gas)

### Session Expiry Fallback

If the session expires during a payment, the hook falls back to requiring the owner's signature (wallet popup / biometrics / PIN):

```typescript theme={null}
const { x402Fetch } = useX402Payment({
  wallet,
  encryptKey: pin,
  getBearerToken: getToken,
  session,  // May be expired
  // If session is expired, falls back to standard wallet signing
});
```

### Best Practices

1. **Set spending limits**: Always configure `maxPaymentAmount` and session `maxCalls`
2. **Whitelist merchants**: Use `allowedRecipients` when possible
3. **Monitor payments**: Use `onPaymentComplete` callback for logging/analytics
4. **Short sessions**: Prefer shorter session durations with renewal over long-lived sessions
5. **Transfer-only**: Always set `allowedEntrypoints: ["transfer"]` for x402 sessions, plus a USDC spending policy: the whitelist allows `transfer` on any token


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.