# x402 payment protocol

Build a full-stack application that pairs Openfort wallet infrastructure with Coinbase's x402 payment protocol to enable seamless HTTP-based USDC micropayments. The recipe supports two wallet approaches — **embedded wallets** for browser-based user payments and **backend wallets** for server-side headless operations — with **gasless transaction** support via either an Openfort paymaster or a Coinbase CDP facilitator.

:::tip
You'll build a React + Express app with HTTP 402-gated content that users unlock by paying USDC on Base. You'll set up both an embedded wallet paywall flow and a backend wallet headless payment flow, with gas sponsored so neither the user nor the server needs ETH.
:::

<VideoSnippet title="x402 Payment Protocol demo" src="mDGlkVwMD-Q" variant="full" />

<HoverCardLink
  title="View Sample Code"
  subtitle="GitHub Repository"
  description="Complete source code for the x402 payment protocol integration with Openfort embedded wallets, React, and Vite."
  href="https://github.com/openfort-xyz/recipes-hub/tree/main/x402"
  img={{
  src: "/img/icons/github-icon.svg",
  alt: "GitHub Icon",
  className: "rounded-none",
}}
  color="#333"
  external
/>

## Getting started

:::note

* Node.js 18+
* [Openfort account](https://dashboard.openfort.io) with Shield credentials
* For backend wallet gas sponsorship: choose **Coinbase CDP** keys ([CDP Developer Portal](https://portal.cdp.coinbase.com)) or **Openfort policy** (from the dashboard)
  :::

::::steps

### Set up your project

Clone the recipe and install dependencies for both frontend and backend:

```bash
pnpx gitpick openfort-xyz/recipes-hub/tree/main/x402 openfort-x402
cd openfort-x402
```

The project has two packages — a React + Vite frontend and an Express.js 5 backend:

```text
x402/
├── backend/              # Express.js 5 API server
│   ├── src/
│   │   ├── server.ts     # Server entry point
│   │   ├── config.ts     # Environment parsing
│   │   ├── routes.ts     # API endpoints
│   │   ├── payment.ts    # x402 payment verification & settlement
│   │   └── openfort.ts   # Openfort client + backend wallet helpers
│   └── .env.example
├── frontend/             # React + Vite application
│   ├── src/
│   │   ├── features/
│   │   │   ├── paywall/          # Embedded wallet paywall flow
│   │   │   └── backend-wallet/   # Backend wallet payment flow
│   │   └── integrations/
│   │       ├── openfort/         # Openfort providers & config
│   │       └── x402/             # x402 protocol helpers
│   └── .env.example
└── README.md
```

### Configure your Openfort credentials

Set up your project in the [Openfort Dashboard](https://dashboard.openfort.io):

1. Create an account or sign in
2. Get your API keys:
   * **Publishable Key** (`pk_test_...`) — used by the frontend
   * **Secret Key** (`sk_test_...`) — used by the backend
3. Set up **Shield** for embedded wallets and note the Shield keys
4. Create a **Policy** for gas sponsorship and note the **Policy ID** (`pol_...`)

:::tip
If you plan to use the backend wallet flow, also create a **Backend Wallet** in the dashboard and note its **Wallet ID** (`acc_...`) and **Wallet Secret Key**.
:::

### Configure your environment

Create the frontend environment file:

```bash
cp frontend/.env.example frontend/.env.local
```

Fill in `frontend/.env.local`:

```bash
VITE_OPENFORT_PUBLISHABLE_KEY=pk_test_...
VITE_OPENFORT_POLICY_ID=pol_...
VITE_OPENFORT_SHIELD_PUBLISHABLE_KEY=your-shield-publishable-key
VITE_CREATE_ENCRYPTED_SESSION_ENDPOINT=http://localhost:3007/api/protected-create-encryption-session
VITE_WALLET_CONNECT_PROJECT_ID=your-wallet-connect-project-id  # Optional — for external wallet connections
VITE_X402_RESOURCE_URL=http://localhost:3007/api/protected-content
VITE_X402_DEFAULT_AMOUNT=0.1
```

Create the backend environment file:

```bash
cp backend/.env.example backend/.env.local
```

Fill in `backend/.env.local`:

```bash
# Server
PORT=3007

# Openfort: Project Keys
OPENFORT_SECRET_KEY=sk_test_...

# Openfort: Shield (for embedded wallet recovery)
OPENFORT_SHIELD_API_KEY=
OPENFORT_SHIELD_SECRET_KEY=
OPENFORT_SHIELD_ENCRYPTION_SHARE=

# Backend wallet (Option B)
OPENFORT_WALLET_SECRET=            # Obtained in Dashboard > API Keys
OPENFORT_BACKEND_WALLET_ID=        # acc_xxx from the dashboard

# Gas sponsorship — choose one:

# Option 1: Coinbase CDP facilitator
X402_FACILITATOR_URL=              # e.g. https://api.cdp.coinbase.com/platform/v2/x402
CDP_API_KEY_ID=                    # From CDP Developer Portal
CDP_API_KEY_SECRET=                # From CDP Developer Portal

# Option 2: Openfort policy (paymaster)
OPENFORT_DELEGATED_ACCOUNT_ID=     # For backend wallet EIP-7702 gas sponsorship
OPENFORT_POLICY_ID=                # Optional; from gas sponsorships tab

# Payment (x402)
PAY_TO_ADDRESS=                    # Recipient address (0x...) for USDC
X402_NETWORK=base-sepolia          # base-sepolia or base
X402_RESOURCE=http://localhost:3007/api/protected-content
X402_DESCRIPTION=Access to premium content
X402_MIME_TYPE=application/json
X402_MAX_AMOUNT=100000             # 6 decimals — 100000 = 0.1 USDC
X402_TIMEOUT=300
X402_ASSET_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7e  # USDC on Base Sepolia
X402_ASSET_NAME=USDC
X402_ASSET_VERSION=2

CORS_ORIGINS=http://localhost:5173,http://localhost:3007
```

:::note
`X402_RPC_URL` is optional — the server auto-resolves public endpoints for `base-sepolia` and `base`.
:::

### Understand the payment flow

Before running the app, here's what happens under the hood. The frontend configures Openfort with Shield for wallet management and a policy for gas sponsorship:

```tsx
// frontend/src/integrations/openfort/OpenfortProviders.tsx
<OpenfortProvider
  publishableKey={import.meta.env.VITE_OPENFORT_PUBLISHABLE_KEY!}
  walletConfig={{
    shieldPublishableKey: import.meta.env.VITE_OPENFORT_SHIELD_PUBLISHABLE_KEY!,
    createEncryptedSessionEndpoint: import.meta.env.VITE_CREATE_ENCRYPTED_SESSION_ENDPOINT,
    ethereum: {
      ethereumFeeSponsorshipId: import.meta.env.VITE_OPENFORT_POLICY_ID,
    },
  }}
  uiConfig={{
    authProviders: [
      AuthProvider.EMAIL_OTP,
      AuthProvider.GUEST,
      AuthProvider.GOOGLE,
      AuthProvider.WALLET,
    ],
    walletRecovery: { defaultMethod: RecoveryMethod.PASSKEY },
  }}
>
  {children}
</OpenfortProvider>
```

When a user accesses protected content, the backend returns HTTP 402 with payment requirements. The frontend then creates an EIP-712 `TransferWithAuthorization` signature (or a direct USDC transfer) and sends it back to unlock the content:

```typescript
// frontend/src/integrations/x402/payments.ts
const domain = {
  name: paymentRequirements.extra?.name ?? 'USD Coin',
  version: paymentRequirements.extra?.version ?? '2',
  chainId: getNetworkId(paymentRequirements.network),
  verifyingContract: getAddress(paymentRequirements.asset),
}

const signature = await client.signTypedData({
  account: accountAddress,
  domain,
  types: AUTHORIZATION_TYPES, // TransferWithAuthorization
  primaryType: 'TransferWithAuthorization',
  message: { from, to, value, validAfter, validBefore, nonce },
})
```

### Run the application

Start both servers in separate terminals:

```bash
# Terminal 1: Backend
cd backend
pnpm i
pnpm dev

# Terminal 2: Frontend
cd frontend
pnpm i
pnpm dev
```

Visit [http://localhost:5173](http://localhost:5173) to see the paywall in action. Sign in, create a wallet, and try paying for the protected content.

::::

## x402 protocol

x402 is an open payment standard developed by Coinbase that leverages the HTTP `402 Payment Required` status code to enable services to charge for API access and content directly over HTTP. This protocol allows:

* **Accountless Payments**: No need for user accounts or sessions
* **Programmatic Access**: Clients can automatically pay for resources
* **Privacy First**: Minimal data sharing required
* **Crypto-Native**: Fast and efficient blockchain-based payments
* **Flexible Monetization**: Pay-per-use model for APIs and content

## Wallet types

The recipe supports two wallet approaches, selectable via tabs in the demo UI.

### Embedded wallet

The **Embedded wallet** uses Openfort's client-side wallet infrastructure. The wallet lives in the user's browser, managed by [Openfort Shield](/docs/products/embedded-wallet) for key management and recovery.

The flow:

1. User authenticates via Openfort (email OTP, Google, guest, or external wallet)
2. Openfort creates a smart account with Shield handling key storage and passkey-based recovery
3. The user signs USDC payments directly in the browser via the Openfort React SDK and wagmi
4. Depending on the gas mode, the transaction is either submitted on-chain (with gas sponsored by an Openfort policy) or signed off-chain as an EIP-3009 `TransferWithAuthorization` for facilitator settlement

This approach is ideal for **user-facing applications** where end users interact with the paywall directly.

### Backend wallet

The **Backend wallet** uses Openfort's server-side wallet API for headless signing. No browser wallet is needed — the server holds the signing key via `OPENFORT_WALLET_SECRET`.

The flow:

1. A backend wallet is created via the Openfort API (or from the demo UI)
2. The server signs EIP-3009 `TransferWithAuthorization` payloads using the backend wallet key
3. Payments are either settled on-chain gaslessly (via Openfort transaction intents) or sent as payment headers for facilitator verification

This approach is ideal for **AI agents**, **automated services**, or any **server-to-server** payment scenario where no human interaction is required.

## Gas sponsorship

Both wallet types support gasless transactions — the payer never needs to hold ETH for gas. The recipe demonstrates two mutually exclusive approaches.

### Via Openfort policy (paymaster)

Openfort's [gas sponsorship](/docs/configuration/gas-sponsorship) uses a paymaster pattern to cover gas costs on behalf of the user or backend wallet.

**Embedded wallet:** When `VITE_OPENFORT_POLICY_ID` is set, the Openfort smart account routes transactions through a paymaster. The user signs a USDC `transfer()` via wagmi, and Openfort's infrastructure sponsors the gas according to the policy rules. The resulting transaction hash is sent to the backend for on-chain verification.

**Backend wallet:** The backend wallet EOA is upgraded to an [EIP-7702 Delegated Account](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization), enabling Openfort transaction intents with gas sponsorship. Set `OPENFORT_DELEGATED_ACCOUNT_ID` (and optionally `OPENFORT_POLICY_ID`) in the backend configuration. The server calls `submitTransferWithAuthorizationGasless()`, which creates an Openfort transaction intent that handles gas via the paymaster.

### Via facilitator (Coinbase CDP)

The x402 facilitator is a third-party service that verifies payment signatures, submits on-chain settlement, and pays gas. The recipe integrates with Coinbase's CDP facilitator.

**Both wallet types:** The wallet signs an EIP-712 `TransferWithAuthorization` off-chain (no gas needed for signing). The backend sends this signed payload to the CDP facilitator's `/verify` and `/settle` endpoints. The facilitator validates the signature, submits the `transferWithAuthorization` call on-chain, and covers the gas cost.

Set `X402_FACILITATOR_URL`, `CDP_API_KEY_ID`, and `CDP_API_KEY_SECRET` (from [CDP Developer Portal](https://portal.cdp.coinbase.com)) in the backend configuration.

## How it works

The recipe supports two payment paths per wallet type, plus the gas sponsorship options above.

**Embedded wallet (browser signs)**

* **Openfort policy (gasless):** User signs a USDC `transfer()`. Openfort's paymaster sponsors gas. Client sends `X-Transaction-Hash` to `/api/protected-content`. Server calls `verifyOnChainPayment`.
* **Facilitator (gasless):** User signs EIP-712 `TransferWithAuthorization` off-chain. Client sends `PAYMENT-SIGNATURE` header. Server calls `verifyWithFacilitator` + `settleWithFacilitator`.

**Backend wallet (server signs)**

* **Openfort policy (gasless):** Client calls `/api/backend-wallet/test-payment?gasMode=openfort-policy`. Server creates a transaction intent via `submitTransferWithAuthorizationGasless` and returns the settlement result directly.
* **Facilitator (gasless):** Client calls `/api/backend-wallet/test-payment?gasMode=facilitator`. Server returns `paymentHeader`. Client sends it to `/api/protected-content`. Server calls `verifyWithFacilitator` + `settleWithFacilitator`.

Both wallet types share the same `/api/protected-content` endpoint and payment verification logic where applicable.

The backend verifies on-chain payments by checking transaction receipts for matching USDC Transfer events:

```typescript
// backend/src/payment.ts
const logs = parseEventLogs({
  abi: erc20Abi,
  eventName: "Transfer",
  logs: receipt.logs,
});

const matchingLog = logs.find(
  (log) =>
    getAddress(log.args.to) === payTo &&
    log.args.value >= requiredAmount,
);
```

## API routes

<div className="flex flex-wrap items-center justify-end gap-3 mb-6">
  <MultiOptionDisplay
    options={[
    { id: 'embedded', label: 'Embedded wallet' },
    { id: 'backend', label: 'Backend wallet' },
  ]}
    defaultSelectedId="embedded"
    className="!pt-0 shrink-0 w-[280px]"
  />
</div>

<span id="embedded" className="hidden [&>*]:mb-6!">
  | Endpoint | Method | Description |
  |----------|--------|-------------|
  | `/api/protected-content` | GET | Protected resource. Returns HTTP 402 and payment requirements. Client retries with `X-Transaction-Hash` (direct transfer) or `PAYMENT-SIGNATURE` (facilitator) to receive content. |
  | `/api/protected-create-encryption-session` | POST | Creates an encrypted session for Openfort Shield (embedded wallet recovery). |
</span>

<span id="backend" className="hidden [&>*]:mb-6!">
  | Endpoint | Method | Description |
  |----------|--------|-------------|
  | `/api/backend-wallet/status` | GET | Returns wallet configuration state, payer address, recipient address, network, and required amount |
  | `/api/backend-wallet/create` | POST | Creates a new Openfort backend wallet; returns id, address, and optional delegatedAccountId |
  | `/api/backend-wallet/upgrade` | POST | Upgrades the configured EOA to an EIP-7702 Delegated Account for gas sponsorship |
  | `/api/backend-wallet/test-payment` | GET | Signs payment with the backend wallet. Accepts `?gasMode=facilitator` or `?gasMode=openfort-policy` to choose the gas sponsorship path. Returns paymentHeader or completes on-chain settlement (Openfort path returns full response). |
</span>

## Use cases

* **API Monetization**: Charge per API call or data access
* **Content Paywalls**: Micropayments for articles, videos, or premium content
* **AI Services**: Pay-per-query for AI models and agents
* **Data Access**: Monetize database queries or data feeds
* **Compute Resources**: Pay-as-you-go cloud computing

## Next steps

* [x402 Protocol Specification](https://github.com/coinbase/x402)
* [Embedded Wallet Guide](/docs/products/embedded-wallet)
* [Server Wallets](/docs/products/server)
* [Gas Sponsorship](/docs/configuration/gas-sponsorship)
* [EIP-7702 Delegated Accounts](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization)
* [Base Network Documentation](https://docs.base.org/)
