Accept x402
Use x402 to charge for an API response, download, or other operation in your application. Your endpoint advertises its price and accepted payment options. The buyer submits payment information, and your integration uses InFlow to verify and settle the payment.
This guide adapts x402 to an existing application. Read How payments work for the roles and request flow, and Accounts and environments for Sandbox setup.
Choose the paid operation
Choose the HTTP method, route, price, and result that the buyer purchases. The examples protect GET /api/report and return a JSON report for 0.01 USDC. Other routes can remain free.
Start with a fixed-price response that your application can generate without changing an order, account, or external system. These examples cover one-time payments, not subscriptions or metered billing.
Configure InFlow
Use a Seller account and Sandbox API key. Keep the key in private server configuration. Pass it to the setup function for your language; never include it in a browser bundle or payment offer.
- The Seller client reads your seller configuration and builds payment offers, including the amount, asset, and recipient.
- The InFlow facilitator is the hosted service that verifies payment information and settles payments. The SDK connects to that service; you do not deploy a facilitator.
- The framework integration checks requests to your paid routes, returns payment requirements, and exchanges payment information with the facilitator.
Create and reuse these clients when your application starts. Keep them available while the application serves requests. Resolve configuration errors before accepting customer traffic.
The examples select the balance scheme, which pays from a buyer’s InFlow balance. The buyer needs Sandbox funds in the advertised currency. This differs from an exact payment signed by a blockchain wallet, or an instrument payment backed by a linked card.
Build the payment offer
An offer tells the buyer what payment the endpoint accepts. A scheme identifies how payment works. The network identifies where that scheme operates. The currency or asset, amount, and recipient identify what must be paid and to whom.
Pass the human-readable price 0.01 USDC to the Seller helper. The helper uses seller configuration to produce the protocol’s asset and amount fields. Do not substitute a guessed wallet address or treat a dollar price as interchangeable with a token amount.
The resulting accepts list contains the available payment options. Multiple entries let the buyer choose a compatible option; the buyer does not pay every entry. Stop setup if the list is empty rather than expose a paid endpoint with no usable payment option.
The Go, Node.js, and Python integrations also register the schemes and networks used by their offers. Keep those registrations consistent with the selected offers. The Rust Axum helper takes the generated route directly and rejects an empty offer list.
Selecting additional schemes does not guarantee that every buyer can pay them. Keep this example on balance while testing; use Prices and offers when adding currencies, networks, or other payment options.
Protect the endpoint
These setup functions return a handler, application, or router for your existing HTTP server. They include their imports and payment setup, but not the server’s listening or shutdown code. Replace the sample report with your own read-only result.
Keep application authentication, permission checks, and request validation ahead of payment processing. Payment establishes that a request was paid; it does not decide who may access private data. Use a fixed-price GET route first, before adding request-dependent prices or POST bodies.
Use inflow-go with the x402 Foundation’s net/http middleware. Call ReportHandler during startup with a bounded context and your private Sandbox Seller API key. Serve the returned handler with your HTTP server.
package report
import (
"context"
"errors"
"fmt"
"net/http"
inflow "github.com/inflowpayai/inflow-go"
"github.com/inflowpayai/inflow-go/x402/seller"
foundation "github.com/x402-foundation/x402/go/v2"
xhttp "github.com/x402-foundation/x402/go/v2/http"
"github.com/x402-foundation/x402/go/v2/http/nethttp"
)
func ReportHandler(ctx context.Context, apiKey string) (http.Handler, error) {
options := inflow.Options{Environment: inflow.Sandbox, APIKey: apiKey}
client, err := seller.New(options)
if err != nil {
return nil, err
}
facilitator, err := seller.NewFacilitator(options)
if err != nil {
return nil, err
}
route, err := client.Route(ctx, seller.RouteOptions{
AcceptsOptions: seller.AcceptsOptions{
Price: seller.PriceSpec{Amount: "0.01 USDC"},
Schemes: []string{"balance"},
},
})
if err != nil {
return nil, err
}
if len(route.Accepts) == 0 {
return nil, errors.New("seller has no matching payment offers")
}
registrations, err := client.SchemeRegistrations(ctx,
seller.RegistrationOptions{Schemes: []string{"balance"}})
if err != nil {
return nil, err
}
resource := xhttp.Newx402HTTPResourceServer(
xhttp.RoutesConfig{"GET /api/report": route},
foundation.WithFacilitatorClient(facilitator),
)
for _, registration := range registrations {
resource.Register(registration.Network, registration.Server)
}
if err := resource.Initialize(ctx); err != nil {
return nil, err
}
mux := http.NewServeMux()
mux.HandleFunc("GET /api/report", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"report":"Your sample report is ready."}`)
})
return nethttp.PaymentMiddlewareFromHTTPServer(resource,
nethttp.WithSyncFacilitatorOnStart(false))(mux), nil
}Initialization checks the registered offers against facilitator support. Automatic startup synchronization is disabled because resource.Initialize already completed. Add free routes to the same router without adding them to the payment route map.
Use @inflowpayai/x402-seller with @x402/express, its required peer packages, and Express. This TypeScript function returns an Express application. Call it with your private Sandbox Seller API key, then start your server.
import express from 'express';
import { paymentMiddlewareFromConfig } from '@x402/express';
import {
createInflowFacilitator, createInflowSellerClient,
inflowRoute, inflowSchemeRegistrations,
} from '@inflowpayai/x402-seller';
export async function createReportApp(apiKey: string) {
const options = { environment: 'sandbox' as const, apiKey };
const seller = await createInflowSellerClient(options);
const facilitator = createInflowFacilitator(options);
const route = await inflowRoute(seller, {
price: '0.01 USDC',
schemes: ['balance'],
});
const accepts = Array.isArray(route.accepts) ? route.accepts : [route.accepts];
if (accepts.length === 0) {
throw new Error('Seller has no matching payment offers.');
}
const registrations = await inflowSchemeRegistrations(seller, {
schemes: ['balance'],
});
const app = express();
app.use(paymentMiddlewareFromConfig(
{ 'GET /api/report': route },
[facilitator],
registrations,
));
app.get('/api/report', (_request, response) => {
response.set('Cache-Control', 'no-store');
response.json({ report: 'Your sample report is ready.' });
});
return app;
}Register the payment middleware before the paid route. If your application already has an Express instance, install the same middleware and handler on that instance. Put your application’s authentication and input checks before the payment middleware.
Use inflowpay with its x402 integration, the upstream x402 FastAPI dependencies, and your ASGI server. The context manager keeps both clients open while your server runs.
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from x402 import x402ResourceServer
from x402.http.middleware.fastapi import payment_middleware
from inflowpay import ClientOptions
from inflowpay.x402.facilitator import Facilitator
from inflowpay.x402.seller import Seller
@asynccontextmanager
async def report_app(api_key: str) -> AsyncIterator[FastAPI]:
options = ClientOptions(environment="sandbox", api_key=api_key)
async with (
await Seller.create(options) as seller,
await Facilitator.create(options) as facilitator,
):
route = await seller.route("0.01 USDC", schemes=["balance"])
if not route.accepts:
raise ValueError("Seller has no matching payment offers.")
resource = x402ResourceServer(facilitator)
for registration in await seller.scheme_registrations(schemes=["balance"]):
resource.register(registration["network"], registration["server"])
app = FastAPI()
app.middleware("http")(
payment_middleware({"GET /api/report": route}, resource)
)
@app.get("/api/report")
async def report() -> JSONResponse:
return JSONResponse(
{"report": "Your sample report is ready."},
headers={"Cache-Control": "no-store"},
)
yield appRun the server inside async with report_app(api_key) as app:. Leaving that context closes the clients. For an existing FastAPI application, keep the clients in its lifespan and install the payment middleware before serving requests.
Use inflow-x402-seller and inflow-x402-axum with Axum and serde_json. Call this function during startup, then serve the returned router. The helper attaches payment handling only to the report route.
use axum::{Json, Router, routing::get};
use inflow_x402_seller::{
Authentication, CancellationToken, ClientOptions, Environment,
Error, OfferOptions, Seller,
};
use serde_json::json;
pub async fn report_router(
api_key: String,
resource_url: &str,
cancellation: &CancellationToken,
) -> Result<Router, Error> {
let seller = Seller::new(ClientOptions {
environment: Environment::Sandbox,
authentication: Authentication::ApiKey(api_key),
..ClientOptions::default()
}, cancellation).await?;
let mut terms = OfferOptions::new("0.01 USDC");
terms.schemes = Some(vec!["balance".into()]);
let route = seller.route(&terms, cancellation).await?;
let layer = inflow_x402_axum::payment_layer(
seller.facilitator(), route, resource_url,
)?;
Ok(Router::new().route(
"/api/report",
get(|| async {
(
[("cache-control", "no-store")],
Json(json!({"report": "Your sample report is ready."})),
)
}).route_layer(layer),
))
}Set resource_url to the complete URL buyers request, such as http://127.0.0.1:3001/api/report in a local test. Use the public HTTPS URL in production, including any externally visible path prefix. Pass your application’s cancellation token to startup calls.
For complete server entry points and dependency setup, see the repositories’ Go, Node.js, Python, and Rust examples. Those examples may advertise more payment options than the balance-only functions here.
Understand verification and settlement
Verification checks whether the submitted payment information is valid for the advertised terms. Settlement completes the payment. A successful verification is not proof that settlement has succeeded.
For the balance-payment integrations shown here, the normal request flow is:
- Without payment information, the endpoint returns 402 Payment Required and the payment requirements. The report handler does not run.
- The buyer repeats the request with payment information. The middleware asks InFlow to verify it against the selected offer.
- After successful verification, the application handler creates the report response.
- The middleware requests settlement before releasing the successful response to the buyer. If settlement succeeds, it includes the payment result in the response headers.
Entering the report handler therefore does not prove that payment has settled. Choose when irreversible actions may occur before adding them to the paid flow. Sending an email or shipping an order from this handler can happen before settlement succeeds. Withholding a response cannot undo application side effects.
Go, Node.js, and Python buffer the response in these integrations. Keep the example response small; these snippets are not a design for streaming or large downloads. If your application needs settlement before work begins, design that flow explicitly rather than assume the middleware has that ordering.
For x402 version 2, PAYMENT-REQUIRED carries the payment requirements, PAYMENT-SIGNATURE carries the buyer’s payment submission, and PAYMENT-RESPONSE carries the settlement result. Let the integration encode these headers. A payment-response header can also describe failure, so its presence alone does not prove success.
Preserve these headers through your proxy and middleware. For cross-origin browser clients, allow the payment request header and expose the payment response headers in your CORS configuration. Keep your application’s login credentials separate; the x402 payment header does not replace them.
Handle failures and repeat requests
- Missing payment: return the payment requirements supplied by the integration. This is the expected first response to an unpaid request.
- Invalid payment: preserve the integration’s rejection. Do not call the report handler yourself as a fallback.
- Application failure: return an appropriate error rather than disguise the failure as a successful report. In these examples, a handler response with a 4xx or 5xx status skips the normal post-handler settlement step.
- Uncertain settlement: a timeout does not establish whether money moved. Preserve the payment information and available transaction references, and investigate the outcome before starting a fresh payment.
Do not replace every error with a new payment challenge or automatically charge again. Keep useful error responses and payment-result headers intact. See Status and recovery for the buyer’s recovery decisions.
Payment replay protection and application delivery are separate concerns. Even if a repeated payment is recognized, your handler may execute again. Use an application record when repeated execution could duplicate work. See Deliver paid results for fulfillment and retry design.
The successful report responses set Cache-Control: no-store. Keep shared caches from returning a paid response to another caller. Review proxy behavior and error responses as part of the integration, not just the route’s happy path.
Check the integration
Start with controlled Sandbox accounts and a buyer funded for the advertised offer. Use an x402-capable buyer or InFlow CLI against the running endpoint. Agree on the destination and spending limit before submitting payment.
For a local server listening on port 3001, log the CLI into your Sandbox buyer account. Use a buyer terminal without the Seller’s INFLOW_API_KEY environment variable, as in the quickstart. Inspect the endpoint without paying:
inflow x402 inspect 'http://127.0.0.1:3001/api/report' --sandbox
Confirm the balance offer’s amount, currency, and recipient before making the purchase. The CLI filters unsupported offers; inspect the raw 402 response when checking every option your endpoint advertises.
inflow x402 pay 'http://127.0.0.1:3001/api/report' --sandbox --scheme balance
This command can make a payment. If it returns an approval link, complete the approval and follow the CLI’s continuation instructions. Confirm that the CLI receives the report and that the seller’s Sandbox dashboard shows the transaction. Replace the URL if your application uses another port or path.
- Request the endpoint without payment. Confirm a 402 response with the intended price, asset, scheme, network, and recipient, and confirm the report handler did not run.
- Submit invalid payment information. Confirm the paid result is not returned and the handler does not run.
- Complete a Sandbox payment. Confirm the report, successful settlement result, and corresponding seller transaction—not just a 200 response.
- Request an unrelated free route. Confirm it remains accessible without payment.
- Exercise an application error and a facilitator failure. Confirm that errors remain errors, private content is not released, and your application does not start another charge automatically.
- Exercise a repeat request. Confirm the behavior of both the payment and the report generation; one does not prove the other is safe to repeat.
Keep these checks alongside your project’s unit tests, integration tests, coverage, and static analysis. Record which scenarios were actually tested. Receiving a payment challenge alone does not establish that verification, settlement, and delivery work.
Continue your integration
Prices and offers
Choose prices, currencies, and the payment options offered to buyers.
Accept both protocols
Offer MPP and x402 without sending one request through two charging paths.
Deliver paid results
Plan fulfillment, repeat requests, and recovery after payment succeeds.