Before you start
| Item | Detail |
|---|---|
| Account | A Seller account and an API key. See Create a Seller account. |
| Environment variables | INFLOW_API_KEY. MPP sellers also need MPP_SECRET_KEY. Generate it with openssl rand -base64 32. Keep it stable across deploys. |
| Environment | The examples use the sandbox. Use production when you go live. The value must match the dashboard that issued the key. |
| Runtime | Python 3.11, Go 1.26, or Rust 1.93, or later. |
Protect an endpoint
- Python
- Go
- Rust
The examples use FastAPI. The clients also work with other asynchronous frameworks.The Python SDK does not support seller subscription endpoints or more than one InFlow offer on one endpoint.
MPP
pip install 'inflowpay[mpp,fastapi]' uvicorn
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from mpp import Credential, Receipt
from mpp.server.decorator import pay
from starlette.responses import JSONResponse
from inflowpay import ClientOptions
from inflowpay.mpp.seller import Seller
@asynccontextmanager
async def application():
async with await Seller.create(
ClientOptions(environment="sandbox", api_key=os.environ["INFLOW_API_KEY"])
) as seller:
app = FastAPI()
@app.get("/report")
@pay(
intent=seller,
method=seller.method,
request=seller.charge_request({"amount": "0.50", "currency": "USDC"}),
realm="api.example.com",
secret_key=os.environ["MPP_SECRET_KEY"],
)
async def report(
request: Request, credential: Credential, receipt: Receipt
) -> JSONResponse:
return JSONResponse(
{"report": "Paid content"},
headers={"Payment-Receipt": receipt.to_payment_receipt()},
)
yield app
x402
pip install 'inflowpay[x402,fastapi]' uvicorn
import asyncio
import os
import uvicorn
from fastapi import FastAPI
from x402 import x402ResourceServer
from x402.http.middleware.fastapi import payment_middleware
from x402.http.types import RouteConfig
from inflowpay import ClientOptions
from inflowpay.x402.facilitator import Facilitator
from inflowpay.x402.seller import Seller
async def serve() -> None:
options = ClientOptions(environment="sandbox", api_key=os.environ["INFLOW_API_KEY"])
async with (
await Seller.create(options) as seller,
await Facilitator.create(options) as facilitator,
):
resource_server = x402ResourceServer(facilitator)
for registration in await seller.scheme_registrations():
resource_server.register(registration["network"], registration["server"])
routes = {
"GET /report": RouteConfig(accepts=await seller.offers("$0.01")),
}
app = FastAPI()
app.middleware("http")(payment_middleware(routes, resource_server))
@app.get("/report")
async def report() -> dict[str, str]:
return {"report": "Your paid report"}
await uvicorn.Server(uvicorn.Config(app, host="127.0.0.1", port=8000)).serve()
asyncio.run(serve())
go get github.com/inflowpayai/inflow-go@v0.3.1
MPP
Protect returns a standard http.Handler. Mount it on your ServeMux.client, err := seller.New(inflow.Options{
Environment: inflow.Sandbox,
APIKey: os.Getenv("INFLOW_API_KEY"),
})
if err != nil {
return err
}
handler, err := client.Protect(seller.Route{
Realm: "api.example.com",
SecretKey: os.Getenv("MPP_SECRET_KEY"),
Offers: []seller.Offer{{Charge: &mpp.ChargeRequest{
Amount: "0.01",
Currency: "USDC",
}}},
}, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("paid response"))
}))
if err != nil {
return err
}
mux.Handle("/paid", handler)
x402
The x402 seller uses the upstream x402nethttp middleware.import (
"context"
"errors"
"net/http"
"os"
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 paidHandler(ctx context.Context, handler http.Handler) (http.Handler, error) {
options := inflow.Options{Environment: inflow.Sandbox, APIKey: os.Getenv("INFLOW_API_KEY")}
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"}},
})
if err != nil { return nil, err }
if len(route.Accepts) == 0 { return nil, errors.New("no payment offers match this route") }
registrations, err := client.SchemeRegistrations(ctx, seller.RegistrationOptions{})
if err != nil { return nil, err }
server := xhttp.Newx402HTTPResourceServer(
xhttp.RoutesConfig{"GET /api/data": route},
foundation.WithFacilitatorClient(facilitator),
)
for _, registration := range registrations {
server.Register(registration.Network, registration.Server)
}
if err := server.Initialize(ctx); err != nil { return nil, err }
return nethttp.PaymentMiddlewareFromHTTPServer(
server, nethttp.WithSyncFacilitatorOnStart(false),
)(handler), nil
}
MPP
Addinflow-mpp-seller, inflow-mpp, and serde_json. Create the seller and an offer.use inflow_mpp_seller::{
Authentication, CancellationToken, ChallengeOptions, ClientOptions,
Environment, Method, Offer, Seller, SellerOptions,
};
use serde_json::json;
async fn configure() -> Result<Offer, Box<dyn std::error::Error>> {
let seller = Seller::new(
ClientOptions {
environment: Environment::Sandbox,
authentication: Authentication::ApiKey(std::env::var("INFLOW_API_KEY")?),
..Default::default()
},
SellerOptions {
realm: "api.example.com".into(),
secret_key: std::env::var("MPP_SECRET_KEY")?,
},
&CancellationToken::new(),
).await?;
Ok(seller.offer(
Method::Inflow,
json!({"amount": "0.10", "currency": "USDC"}),
ChallengeOptions {
description: Some("One search request".into()),
requires_auth: true,
..Default::default()
},
)?)
}
402 challenge. A paid request gets a receipt.use inflow_mpp_seller::{
CancellationToken, Error, Offer, decode_credential, render_challenge,
};
async fn payment_response(
offer: &Offer,
payment_authorization: Option<&str>,
body: &[u8],
cancellation: &CancellationToken,
) -> Result<(u16, &'static str, String, &'static str), Error> {
let Some(header) = payment_authorization else {
let challenge = offer.challenge(Some(body))?;
return Ok((402, "WWW-Authenticate", render_challenge(&challenge)?, ""));
};
let (_, encoded) = header.split_once(' ')
.filter(|(scheme, _)| scheme.eq_ignore_ascii_case("Payment"))
.ok_or_else(|| Error::new("INVALID_MPP_DATA", "expected Payment credential"))?;
let credential = decode_credential(encoded.trim())?;
let receipt = offer.accept(&credential, Some(body), cancellation).await?;
let header = inflow_mpp::encode(&serde_json::to_value(receipt)
.map_err(|_| Error::new("INVALID_MPP_DATA", "invalid receipt"))?)?;
// Run the paid operation only after accept returns a receipt.
Ok((200, "Payment-Receipt", header, "paid result"))
}
x402 with Axum
inflow-x402-axum adds a payment layer to an Axum endpoint.use axum::{Router, routing::get};
use inflow_x402_axum::payment_layer;
use inflow_x402_seller::{Seller, OfferOptions, CancellationToken};
async fn example(seller: Seller) -> Result<Router, Box<dyn std::error::Error>> {
let route = seller.route(&OfferOptions::new("$0.01"),
&CancellationToken::new()).await?;
let layer = payment_layer(seller.facilitator(), route,
"https://service.example/report")?;
let app = Router::new().route("/report",
get(|| async { "Your report" }).route_layer(layer));
Ok(app)
}
upto endpoints, because the upstream middleware settles the full authorized amount.Next step
Test a payment
Confirm the
402 challenge. Then pay your endpoint with the CLI in the sandbox.