Make Payments

Pay with x402

Use the InFlow buyer SDK to pay for an x402-protected resource from your application. The SDK gets a signed payment through InFlow; your application sends that payment to the seller and reads the result.

Start with a known-price Sandbox endpoint. A payment-aware request can initiate a purchase when the seller returns a compatible offer. Use an ordinary HTTP request when you only want to inspect the price.

Prepare your buyer account

  • Create an InFlow API key in Sandbox. A Developer account can buy; an existing Seller account can also buy.
  • Fund the buyer’s Sandbox account in the currency offered by the test endpoint.
  • Use a separate seller account for the endpoint and keep access to the buyer’s dashboard for approval.

Follow Accounts and environments for account and key setup. Load the API key from your application’s secret configuration. The examples use InFlow-managed payments and do not need a wallet private key in your application.

Your API key authenticates to InFlow, not to the seller. If the seller requires login or another credential, supply that separately. The examples call a payment-only endpoint; adding payment does not grant unrelated application permissions.

Understand the request sequence

The examples use x402 version 2. A seller that requires payment returns 402 Payment Required with a Payment-Required header. The decoded document contains the resource information and an accepts list of payment options.

  • Request the resource and read the seller’s payment requirements.
  • Select an offer your application accepts and InFlow supports.
  • Request a signed payment through InFlow and wait for approval when needed.
  • Send the same resource request with the signed payment in Payment-Signature.
  • Read the HTTP response, purchased content, and any Payment-Response settlement result.

Signing prepares the payment for submission. It does not establish that settlement or delivery succeeded. The seller processes the submitted payment and returns the outcome. See Understand verification and settlement for the seller-side sequence.

An endpoint can also return a free response or a nonpayment error. The examples return or report that response without creating a payment unless the initial status is 402.

Make a payment from code

Run the report endpoint from Accept x402 on port 3001. It offers GET /api/report for 0.01 USDC through the balance scheme. This example pays from the buyer’s InFlow balance, not a linked card.

Add the function for your language to your application. Pass the Sandbox buyer’s API key and http://127.0.0.1:3001/api/report. Each function returns response text and prints the seller’s settlement reference when supplied. Errors propagate to the caller. These are callable integration examples, not standalone command-line programs.

Use Go 1.26 or later. Install the buyer package in your module:

go get github.com/inflowpayai/inflow-go/x402/buyer@v0.3.2

Client.Do makes the initial request, selects a supported offer, waits for its signed payment, and sends one paid retry. The client does not follow redirects. Pass your application’s context so cancellation can stop the operation.

package payments

import (
    "context"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"

    inflow "github.com/inflowpayai/inflow-go"
    "github.com/inflowpayai/inflow-go/x402"
    "github.com/inflowpayai/inflow-go/x402/buyer"
)

func BuyReport(ctx context.Context, apiKey, url string) (string, error) {
    ctx, cancel := context.WithTimeout(ctx, 15*time.Minute)
    defer cancel()
    client, err := buyer.New(buyer.Options{Options: inflow.Options{
        Environment: inflow.Sandbox, APIKey: apiKey,
    }})
    if err != nil {
        return "", err
    }
    request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return "", err
    }
    response, err := client.Do(request, buyer.SignOptions{})
    if err != nil {
        return "", err
    }
    defer response.Body.Close()
    if encoded := response.Header.Get(x402.HeaderPaymentResponse); encoded != "" {
        data, err := base64.StdEncoding.DecodeString(encoded)
        if err != nil {
            return "", err
        }
        var settled x402.SettleResponse
        if err := json.Unmarshal(data, &settled); err != nil {
            return "", err
        }
        if !settled.Success {
            return "", fmt.Errorf("seller reported failed settlement; check the purchase before retrying")
        }
        fmt.Println("Seller settlement:", settled.Network, settled.Transaction)
    }
    if response.StatusCode < 200 || response.StatusCode >= 300 {
        return "", fmt.Errorf("HTTP %d; check the purchase before retrying", response.StatusCode)
    }
    body, err := io.ReadAll(response.Body)
    return string(body), err
}

Use Node.js 22 or later and TypeScript. Install the buyer package and its required x402 peer dependency:

npm install @inflowpayai/x402-buyer@0.11.2 @x402/core@2.27.0

createInflowClient provides InFlow signing. x402HTTPClient decodes payment requirements and builds the payment header. The function uses ordinary fetch for both resource requests, with redirects disabled and a shared cancellation signal.

import { createInflowClient } from '@inflowpayai/x402-buyer';
import { x402HTTPClient } from '@x402/core/client';

export async function buyReport(apiKey: string, url: string): Promise<string> {
  const signal = AbortSignal.timeout(900_000);
  const core = await createInflowClient({
    apiKey, environment: 'sandbox',
    signDefaults: { signal, timeoutMs: 900_000 },
  });
  const payments = new x402HTTPClient(core);
  let response = await fetch(url, { redirect: 'error', signal });
  if (response.status === 402) {
    const required = payments.getPaymentRequiredResponse(
      (name) => response.headers.get(name),
    );
    await response.body?.cancel();
    const payload = await payments.createPaymentPayload(required);
    response = await fetch(url, {
      headers: payments.encodePaymentSignatureHeader(payload),
      redirect: 'error', signal,
    });
  }
  if (response.headers.has('Payment-Response')) {
    const settled = payments.getPaymentSettleResponse(
      (name) => response.headers.get(name),
    );
    if (!settled.success) {
      throw new Error('Seller reported failed settlement; check the purchase before retrying');
    }
    console.log('Seller settlement:', settled.network, settled.transaction);
  }
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}; check the purchase before retrying`);
  }
  return response.text();
}

This example registers no external wallet signer or payment-recovery hook. A second payment requirement does not start another purchase.

Use Python 3.11 or later. Install the x402 dependencies in your virtual environment:

python -m pip install 'inflowpay[x402]==0.3.2'

Buyer supplies InFlow signing to x402AsyncTransport. The transport submits the signed payment after a compatible challenge. This example registers no recovery hook that could create a replacement payment.

import asyncio

import httpx
from inflowpay import ClientOptions
from inflowpay.x402.buyer import Buyer
from x402.http import decode_payment_response_header
from x402.http.clients.httpx import x402AsyncTransport


async def buy_report(api_key: str, url: str) -> str:
    async with (
        await Buyer.create(
            ClientOptions(environment="sandbox", api_key=api_key)
        ) as buyer,
        httpx.AsyncClient(
            transport=x402AsyncTransport(buyer),
            follow_redirects=False,
            timeout=30,
        ) as http,
    ):
        async with asyncio.timeout(900):
            response = await http.get(url)
        encoded = response.headers.get("Payment-Response")
        if encoded is not None:
            settled = decode_payment_response_header(encoded)
            if not settled.success:
                raise ValueError(
                    "Seller reported failed settlement; check the purchase before retrying"
                )
            print(f"Seller settlement: {settled.network} {settled.transaction}")
        response.raise_for_status()
        return response.text

The context managers close both clients. The request has a 15-minute overall deadline and 30-second HTTP timeouts. Redirects are disabled.

Use Rust 1.93 or later. Add these dependencies to your Cargo project:

cargo add inflow-x402@=0.4.1 inflow-x402-buyer@=0.4.1
cargo add reqwest@0.13 --no-default-features --features rustls
cargo add serde_json@1
cargo add tokio@1 --features rt-multi-thread,macros

The explicit flow selects an offer, prepares a payment, and waits for its signed payload. It exposes the InFlow transaction ID before waiting. The HTTP client sends the payload once and does not follow redirects or automatically retry requests.

use inflow_x402_buyer::{
    Authentication, Buyer, BuyerOptions, CancellationToken, ClientOptions, Environment,
    OriginalJson, PaymentRequired, SignOptions, WaitOptions,
};
use std::time::Duration;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;

pub async fn buy_report(
    api_key: &str,
    url: &str,
    cancellation: &CancellationToken,
) -> Result<String> {
    let http = reqwest::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .retry(reqwest::retry::never())
        .timeout(Duration::from_secs(30))
        .build()?;
    let mut response = http.get(url).send().await?;
    if response.status() == 402 {
        let encoded = response
            .headers()
            .get("Payment-Required")
            .ok_or("Missing Payment-Required header")?
            .to_str()?;
        let required: PaymentRequired<OriginalJson> =
            serde_json::from_value(inflow_x402::decode(encoded)?)?;
        drop(response);
        let buyer = Buyer::new(
            BuyerOptions {
                client: ClientOptions {
                    environment: Environment::Sandbox,
                    authentication: Authentication::ApiKey(api_key.to_owned()),
                    ..Default::default()
                },
                ..Default::default()
            },
            cancellation,
        )
        .await?;
        let selected = buyer
            .select(&required, cancellation)
            .await?
            .ok_or("No supported InFlow offer; no payment was started")?;
        let payment = buyer
            .prepare(&selected, &required, SignOptions::default(), cancellation)
            .await?;
        println!("InFlow transaction: {}", payment.transaction_id());
        let signed = payment.wait(WaitOptions::default()).await?;
        if cancellation.is_cancelled() {
            return Err("Cancelled before submission; check the existing transaction".into());
        }
        let mut header = reqwest::header::HeaderValue::from_str(&signed.encoded_payload)?;
        header.set_sensitive(true);
        response = http
            .get(url)
            .header("Payment-Signature", header)
            .send()
            .await?;
    }
    if let Some(encoded) = response.headers().get("Payment-Response") {
        let settled = inflow_x402::decode(encoded.to_str()?)?;
        if settled["success"] != true {
            return Err(
                "Seller reported failed settlement; check the purchase before retrying".into(),
            );
        }
        println!(
            "Seller settlement: {} {}",
            settled["network"], settled["transaction"]
        );
    }
    if !response.status().is_success() {
        return Err(format!(
            "HTTP {}; check the purchase before retrying",
            response.status()
        )
        .into());
    }
    Ok(response.text().await?)
}

Pass your application’s CancellationToken to stop SDK work and prevent submission after cancellation is observed. Resource HTTP requests use their own 30-second timeout. The default SDK payment wait is 15 minutes.

For POST, preserve the destination, method, body, and required application headers when adding the payment header. Keep a reusable body rather than a consumed stream. Go’s request helper requires Request.GetBody for a paid body replay; request constructors set it for supported readers such as bytes.Reader.

Use HTTPS for remote sellers. Local HTTP is appropriate for this localhost test. Keep your InFlow API key out of seller requests and never log signed payment payloads.

Choose acceptable payment terms

Each entry in accepts describes a complete payment option. Keep its terms together when choosing what to pay:

  • scheme identifies how payment works, such as balance, exact, or instrument.
  • network identifies where that payment operates.
  • asset and amount identify what is paid and how much. Interpret the amount in that asset’s units, not as an unqualified dollar amount.
  • payTo identifies the payment recipient.

The InFlow buyers prefer balance and then exact by default, subject to supported offers. That preference is not a price limit and does not select the cheapest offer. Linked-card payments need the separate instrument configuration described in Linked cards.

These functions rely on a controlled seller with a known price. Before accepting arbitrary endpoints, restrict the schemes, networks, assets, recipients, and amounts your application permits. Apply those rules before creating a signed payment. Use Choose payment options for selection controls and Networks and currencies for supported combinations.

If no acceptable option remains, report that outcome without paying. Do not change an advertised option’s network or recipient to force a match. External-wallet signing is a separate integration covered by External wallets.

Wait for approval

InFlow may require the buyer to approve the purchase before a signed payment becomes available. Tell the buyer that approval may be needed and keep the application responsive while it waits. An applicable approval policy can permit a purchase without a fresh manual approval.

A prepared-payment flow is useful when your interface needs to display progress or retain the transaction ID before waiting. Node exposes prepareInflowPayment followed by awaitPayload. Go and Rust expose Prepare/Wait and prepare/wait respectively; Python exposes prepare followed by await_payload.

These operations prepare and wait for the same purchase. They are not a reason to repeatedly create new transactions while approval is pending. See Approvals and allowances for the owner’s decisions and spending permissions.

Read the response and settlement result

Check the resource’s HTTP status and parse its body according to the endpoint’s contract. The examples return text; an application expecting a report can then parse and validate the report’s JSON fields.

When Payment-Response is present, decode it and check success. Its network and transaction fields identify the seller-reported settlement. Retain those values for support and reconciliation. That reference is not necessarily the InFlow transaction ID created during approval.

A settlement result is the seller’s report, not independent verification of the transfer. It also does not establish that the seller returned the correct product. Check the purchased content separately from payment success.

A free response can legitimately omit the settlement header. After payment submission, a missing or unreadable header leaves the outcome uncertain. The examples decode the header only when present; your application must not interpret its absence as proof that no payment occurred.

Recover from an interrupted purchase

Stop and inspect the existing purchase after a second 402, a settlement failure, or a lost response. A timeout after submission can happen after the seller has accepted payment. Calling the purchase function again can create another payment.

If your application needs recovery after a restart, retain the InFlow transaction ID, original request details, and available settlement reference. Use an explicit payment flow to collect those identifiers. Store any retained signed payload securely; the convenience functions do not provide durable recovery storage.

Stopping local waiting or cancelling an approval does not reverse a completed payment. Determine the existing outcome before deciding whether to resume delivery or make a genuinely new purchase. Follow Status and recovery rather than adding a generic retry loop around the entire payment.

Test the complete purchase

Run your buyer code against the Sandbox seller and confirm both the report and the recorded transaction. A signed payload alone does not prove that the seller accepted payment.

  • Complete the known-price purchase and inspect the HTTP response, settlement result, and InFlow transaction.
  • Reject a requested approval and confirm that the buyer stops before submitting a signed payment.
  • Offer no supported payment option. Confirm that the buyer reports the mismatch without creating a payment.
  • After adding spending controls, test an offer that exceeds the permitted amount or uses an excluded network.
  • Return a second 402 or interrupt the paid response. Confirm that the application does not automatically start another purchase.
  • Return a free response without a settlement header. Confirm that optional settlement decoding does not reject it.

Keep failure tests alongside your project’s unit tests, integration tests, and coverage requirements. A simulated settlement response helps exercise your error handling, but it does not replace a real Sandbox purchase. See Test your integration for the broader checklist.

Continue your integration

Choose payment options

Limit purchases to acceptable prices, payment schemes, networks, and assets.

Approvals and allowances

Guide the buyer through required approval and spending permissions.

Status and recovery

Check an existing purchase when payment or delivery is interrupted.