# 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)