# 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
\ncom.stripe\nstripe-java\n{VERSION}\n
```
#### 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/v84
```
#### 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")
stripe.api_key = STRIPE_SECRET_KEY
stripe.api_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 = stripe.PaymentIntent.create(
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 {
// 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)