# x402 quickstart

Build a server that accepts x402 payments.

## x402 payment endpoint builder 

Learn how to build and deploy a server endpoint that accepts x402 payments for machine-to-machine transactions.

### Install the Stripe Node library

Install the package and import it in your code. Alternatively, if you’re starting from scratch and need a package.json file, download the project files using the Download link in the code editor.

#### npm

Install the library:

```bash
npm install --save stripe
```

#### GitHub

Or download the stripe-node library source code directly [from GitHub](https://github.com/stripe/stripe-node).

### Install the Stripe Ruby library

Install the Stripe ruby gem and require it in your code. Alternatively, if you’re starting from scratch and need a Gemfile, download the project files using the link in the code editor.

#### Terminal

Install the gem:

```bash
gem install stripe
```

#### Bundler

Add this line to your Gemfile:

```bash
gem 'stripe'
```

#### GitHub

Or download the stripe-ruby gem source code directly [from GitHub](https://github.com/stripe/stripe-ruby).

### Install the Stripe Java library

Add the dependency to your build and import the library. Alternatively, if you’re starting from scratch and need a sample pom.xml file (for Maven), download the project files using the link in the code editor.

#### Maven

Add the following dependency to your POM and replace {VERSION} with the version number you want to use.

```bash
<dependency>\n<groupId>com.stripe</groupId>\n<artifactId>stripe-java</artifactId>\n<version>{VERSION}</version>\n</dependency>
```

#### Gradle

Add the dependency to your build.gradle file and replace {VERSION} with the version number you want to use.

```bash
implementation "com.stripe:stripe-java:{VERSION}"
```

#### GitHub

Download the JAR directly [from GitHub](https://github.com/stripe/stripe-java/releases/latest).

### Install the Stripe Python package

Install the Stripe package and import it in your code. Alternatively, if you’re starting from scratch and need a requirements.txt file, download the project files using the link in the code editor.

#### pip

Install the package through pip:

```bash
pip3 install stripe
```

#### GitHub

Download the stripe-python library source code directly [from GitHub](https://github.com/stripe/stripe-python).

### Install the Stripe PHP library

Install the library with composer and initialize with your secret API key. Alternatively, if you’re starting from scratch and need a composer.json file, download the files using the link in the code editor.

#### Composer

Install the library:

```bash
composer require stripe/stripe-php
```

#### GitHub

Or download the stripe-php library source code directly [from GitHub](https://github.com/stripe/stripe-php).

### Set up your server

Add the dependency to your build and import the library. Alternatively, if you’re starting from scratch and need a go.mod file, download the project files using the link in the code editor.

#### Go

Make sure to initialize with Go Modules:

```bash
go get -u github.com/stripe/stripe-go/v85
```

#### GitHub

Or download the stripe-go module source code directly [from GitHub](https://github.com/stripe/stripe-go).

### Install the Stripe.net library

Install the package with .NET or NuGet. Alternatively, if you’re starting from scratch, download the files which contains a configured .csproj file.

#### dotnet

Install the library:

```bash
dotnet add package Stripe.net
```

#### NuGet

Install the library:

```bash
Install-Package Stripe.net
```

#### GitHub

Or download the Stripe.net library source code directly [from GitHub](https://github.com/stripe/stripe-dotnet).

### Install the Stripe libraries

Install the packages and import them in your code. Alternatively, if you’re starting from scratch and need a `package.json` file, download the project files using the link in the code editor.

Install the libraries:

```bash
npm install --save stripe @stripe/stripe-js next
```

### Initialize Stripe

Set up the Stripe client with your secret key and set the API version to `2026-03-04.preview`. The client handles payment processing and provides crypto deposit addresses for x402 payments.

Make sure you have [crypto payins enabled](https://support.stripe.com/questions/get-started-with-pay-with-crypto) on your Stripe account.

### Configure the facilitator

The facilitator verifies payment proofs and settles transactions on-chain. For testing, use the x402.org testnet facilitator. In production, you can run your own facilitator or use a trusted third-party service.

### Create the PaymentIntent handler

Create a function that determines where to send payments. Either extract the address from an existing payment header for retries or verification, or [create a new PaymentIntent](https://docs.stripe.com/api/payment_intents/create.md) to generate a new deposit address.

When you create a [PaymentIntent](https://docs.stripe.com/api/payment_intents.md) with the `crypto` *payment method* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) and `deposit` mode, specify the networks you want to support using `deposit_options`. Stripe returns deposit addresses for the requested networks. The function extracts the Base network address clients use to send *USDC* (A cryptocurrency that's pegged to the value of a fiat currency or other asset in order to limit volatility).

### Add payment middleware

Add middleware to protect your endpoint and declare the payment requirements. Configure:

- `scheme`: Set the payment scheme. Use `exact` for exact-amount payments.
- `price`: Set the cost per request, for example, 0.01 USD.
- `network`: Set the blockchain network, for example, `eip155:84532` for the Base Sepolia testnet.
- `payTo`: Provide a function that returns the deposit address.

### Create your protected endpoint

Define the endpoint that requires payment. The middleware automatically:

- Returns a `402 Payment Required` response with payment details when the request doesn’t include a valid payment.
- Verifies payment proofs through the facilitator.
- Allows access after the facilitator confirms the payment.

### Run the server

Build and run your server to test the endpoint at `http://localhost:4242/paid`.

```bash
npm run dev
```

### Run the server

Build and run your server to test the endpoint at `http://localhost:4242/paid`.

```bash
uv run python main.py
```

### Test without payment

Make a request to your server without payment to confirm it returns a `402` status code with payment details.

```bash
curl http://localhost:4242/paid
```

You’ll see a `402 Payment Required` response with x402 payment information, including the deposit address.

### Test with payment

Use Stripe’s [purl](https://github.com/stripe/purl) tool to test the full payment flow. The tool handles the x402 protocol and completes the payment automatically.

```bash
purl http://localhost:4242/paid
```

If you have a wallet connected with testnet USDC, the server returns the content after successful payment. You can confirm the payment in the [Stripe Dashboard](https://dashboard.stripe.com) under **Payments**.

## Confirm success

You have a working endpoint that requires payments for access.

- Node.js 20+
- npm or pnpm package manager
- Stripe account with crypto payments enabled
- EVM wallet with testnet USDC
- Python 3.10+
- [uv](https://github.com/astral-sh/uv) package manager
- Stripe account with crypto payments enabled
- EVM wallet with testnet USDC
1. Install dependencies

~~~
npm install
~~~

2. Configure environment variables

~~~
cp .env.template .env
\# Edit .env with your credentials
~~~

3. Run the server

~~~
npm run dev
~~~
1. Install dependencies

~~~
uv sync
~~~

2. Configure environment variables

~~~
cp .env.template .env
\# Edit .env with your credentials
~~~

3. Run the server

~~~
uv run python main.py
~~~
import stripe
\# Stripe handles payment processing and provides the crypto deposit address.
STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY")
if not STRIPE_SECRET_KEY:
    raise ValueError("STRIPE_SECRET_KEY environment variable is required")

client = stripe.StripeClient(
    STRIPE_SECRET_KEY,
    stripe_version="2026-03-04.preview",
)
stripe.set_app_info(
    "stripe-samples/machine-payments",
    url="https://github.com/stripe-samples/machine-payments",
    version="1.0.0",
)
\# The facilitator verifies payment proofs and settles transactions on-chain.
# In this example, we use the x402.org testnet facilitator.
FACILITATOR_URL = os.getenv("FACILITATOR_URL", "https://x402.org/facilitator")
facilitator = HTTPFacilitatorClient(FacilitatorConfig(url=FACILITATOR_URL))

# Set up resource server and register the payment scheme handler for Base Sepolia
server = x402ResourceServer(facilitator)
server.register("eip155:84532", ExactEvmServerScheme())
valid_pay_to_addresses: set[str] = set()


async def create_pay_to_address(context: Any) -> str | None:
    """
    This function determines where payments should be sent. It either:
    1. Extracts the address from an existing payment header (for retry/verification), or
    2. Creates a new Stripe PaymentIntent to generate a fresh deposit address.
    """
    \# If a payment header exists, extract the destination address from it
    payment_header = getattr(context, "payment_header", None) or getattr(
        context, "paymentHeader", None
    )
    if payment_header:
        try:
            decoded = json.loads(base64.b64decode(payment_header).decode())
            to_address = decoded.get("payload", {}).get("authorization", {}).get("to")

            if to_address and isinstance(to_address, str):
                if to_address.lower() not in valid_pay_to_addresses:
                    raise ValueError("Invalid payTo address: not found in server cache")
                return to_address.lower()
        except (json.JSONDecodeError, base64.binascii.Error):
            pass

        return None

    # Create a new PaymentIntent to get a fresh crypto deposit address
    decimals = 6  # USDC has 6 decimals
    amount_in_cents = int(10000 / (10 ** (decimals - 2)))

    payment_intent = client.v1.payment_intents.create(params={
        "amount": amount_in_cents,
        "currency": "usd",
        "payment_method_types": ["crypto"],
        "payment_method_data": {"type": "crypto"},
        "payment_method_options": {
            "crypto": {
                "mode": "deposit",
                "deposit_options": {
                    "networks": ["base"],
                },
            }
        },
        "confirm": True,
    })

    next_action = payment_intent.get("next_action", {})
    deposit_details = next_action.get("crypto_display_details", {})

    if not deposit_details:
        raise ValueError("PaymentIntent did not return expected crypto deposit details")

    # Extract the Base network deposit address from the PaymentIntent
    deposit_addresses = deposit_details.get("deposit_addresses", {})
    base_address = deposit_addresses.get("base", {})
    pay_to_address = base_address.get("address")

    if not pay_to_address:
        raise ValueError("PaymentIntent did not return expected crypto deposit details")

    print(
        f"Created PaymentIntent {payment_intent['id']} "
        f"for ${amount_in_cents / 100:.2f} -> {pay_to_address}"
    )

    valid_pay_to_addresses.add(pay_to_address.lower())
    return pay_to_address
\# Define resource configuration for the x402 payment middleware
routes = {
    # Define pricing for protected endpoints
    "GET /paid": RouteConfig(
        accepts=[
            PaymentOption(
                scheme="exact",  # Exact amount payment scheme
                price="$0.01",  # Cost per request
                network="eip155:84532",  # Base Sepolia testnet
                pay_to=create_pay_to_address,  # Dynamic address resolution
            )
        ],
        description="Data retrieval endpoint",
        mime_type="application/json",
    )
}
\# Create FastAPI app
app = FastAPI(title="x402 REST API")

# Add x402 middleware
app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server)


# This endpoint is only accessible after valid payment is verified.
@app.get("/paid")
async def get_paid():
    return {"foo": "bar"}
if __name__ == "__main__":
    print("Server listening at http://localhost:4242")
    uvicorn.run(app, host="0.0.0.0", port=4242)
// Stripe handles payment processing and provides the crypto deposit address.
if (!process.env.STRIPE_SECRET_KEY) {
  console.error("❌ STRIPE_SECRET_KEY environment variable is required");
  process.exit(1);
}

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", {
  apiVersion: "2026-03-04.preview" as any,
  appInfo: {
    name: "stripe-samples/machine-payments",
    url: "https://github.com/stripe-samples/machine-payments",
    version: "1.0.0",
  },
});
// The facilitator verifies payment proofs and settles transactions on-chain.
// In this example, we use the x402.org testnet facilitator.
const facilitatorUrl = process.env.FACILITATOR_URL;
if (!facilitatorUrl) {
  console.error("❌ FACILITATOR_URL environment variable is required");
  process.exit(1);
}
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
// In-memory cache for deposit addresses (TTL: 5 minutes)
// NOTE: For production, use a distributed cache like Redis instead of node-cache
const paymentCache = new NodeCache({ stdTTL: 300, checkperiod: 60 });

// This function determines where payments should be sent. It either:
// 1. Extracts the address from an existing payment header (for retry/verification), or
// 2. Creates a new Stripe PaymentIntent to generate a fresh deposit address.
async function createPayToAddress(context: any): Promise<string> {
  // If a payment header exists, extract the destination address from it
  if (context.paymentHeader) {
    const decoded = JSON.parse(
      Buffer.from(context.paymentHeader, "base64").toString(),
    );
    const toAddress = decoded.payload?.authorization?.to;

    if (toAddress && typeof toAddress === "string") {
      if (!paymentCache.has(toAddress)) {
        throw new Error("Invalid payTo address: not found in server cache");
      }
      return toAddress;
    }

    throw new Error(
      "PaymentIntent did not return expected crypto deposit details",
    );
  }

  // Create a new PaymentIntent to get a fresh crypto deposit address
  const decimals = 6; // USDC has 6 decimals
  const amountInCents = Number(10000) / Math.pow(10, decimals - 2);

  const paymentIntent = await stripe.paymentIntents.create({
    amount: amountInCents,
    currency: "usd",
    payment_method_types: ["crypto"],
    payment_method_data: {
      type: "crypto",
    },
    payment_method_options: {
      crypto: {
        mode: "deposit",
        deposit_options: {
          networks: ["base"],
        },
      },
    },
    confirm: true,
  });

  if (
    !paymentIntent.next_action ||
    !("crypto_display_details" in paymentIntent.next_action)
  ) {
    throw new Error(
      "PaymentIntent did not return expected crypto deposit details",
    );
  }

  // Extract the Base network deposit address from the PaymentIntent
  const depositDetails = paymentIntent.next_action
    .crypto_display_details as any;
  const payToAddress = depositDetails.deposit_addresses["base"]
    .address as string;

  console.log(
    `Created PaymentIntent ${paymentIntent.id} for $${(
      amountInCents / 100
    ).toFixed(2)} -> ${payToAddress}`,
  );

  paymentCache.set(payToAddress, true);
  return payToAddress;
}
// The middleware protects the route and declares the payment requirements.
app.use(
  paymentMiddleware(
    {
      // Define pricing for protected endpoints
      "GET /paid": {
        accepts: [
          {
            scheme: "exact", // Exact amount payment scheme
            price: "$0.01", // Cost per request
            network: "eip155:84532", // Base Sepolia testnet
            payTo: createPayToAddress, // Dynamic address resolution
          },
        ],
        description: "Data retrieval endpoint",
        mimeType: "application/json",
      },
    },
    // Register the payment scheme handler for Base Sepolia
    new x402ResourceServer(facilitatorClient).register(
      "eip155:84532",
      new ExactEvmScheme(),
    ),
  ),
);
// This endpoint is only accessible after valid payment is verified.
app.get("/paid", (c) => {
  return c.json({
    foo: "bar",
  });
});
serve({
  fetch: app.fetch,
  port: 4242,
});

console.log(`Server listening at http://localhost:4242`);
## Next steps

- [Machine payments](https://docs.stripe.com/payments/machine.md)
- [x402 payments](https://docs.stripe.com/payments/machine/x402.md)
