Accept Payments

Accept MPP

Use MPP to charge for an API response, download, or other operation in your application. Your endpoint tells the buyer how to pay, checks the returned payment credential, and delivers the result after payment succeeds.

This page adapts payment handling to an existing application. Start with Accept a payment if you need a complete runnable server, installation commands, and a Sandbox purchase.

Choose the paid operation

Choose the HTTP method and route, the result the buyer purchases, and its price. For example, GET /api/report can return one report for 0.01 USDC. Register payment handling on that route; leave unrelated routes unchanged.

Decide what a successful purchase delivers before adding the payment check. A report download, a generated answer, and a submitted job have different completion conditions. The payment code does not decide those conditions for your application.

The examples below use a fixed-price report with the charge intent: a one-time payment. They do not create a subscription or bill measured usage.

Configure the seller

  • Use a Seller API key from the environment you intend to test. Follow Accounts and environments for account and key setup.
  • Keep a separate, private challenge-signing secret. Reuse it across requests, restarts, and application instances that accept the same challenges.
  • Initialize and reuse the Seller client or payment method. The SDK reads seller configuration from InFlow; do not create a new client for every request.
  • Select the payment method and price your seller can accept. The examples use Sandbox, the inflow method, and 0.01 USDC from the buyer’s InFlow balance.

MPP is the protocol; inflow, tempo, stripe, and card identify different payment methods. Adding the SDK does not enable every method for your seller. Each method needs its corresponding account, currency, and payment configuration.

For another payment option, read Prices and offers and the relevant setup page under Ways to pay. For example, Stripe payments explains the connected seller account. The inflow method’s linked-card option is a separate integration from the stripe method.

Add the payment check

Keep your application’s permission checks and input validation before the step that accepts payment. Confirm that the requested operation is available. Perform the paid work only after payment has been accepted.

The following fragments show where payment handling belongs. They use the configuration and imports from the runnable quickstart; they are not standalone applications. Replace the sample report response with your application’s result.

Use the Go SDK with the standard HTTP server. Here, client is an initialized Seller client, secret is the signing secret, and mux is your application’s router.

offer := seller.Offer{Charge: &mpp.ChargeRequest{
    Amount: "0.01", Currency: "USDC",
}}
opaque := "R0VUIC9hcGkvcmVwb3J0"
paid, err := client.Protect(seller.Route{
    Realm:     "report-service",
    SecretKey: secret,
    Offers:    []seller.Offer{offer},
    Opaque:    &opaque,
}, http.HandlerFunc(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."}`)
}))
if err != nil {
    return err
}
mux.Handle("GET /api/report", paid)

Protect checks and accepts payment before calling the enclosed handler. It adds the receipt to the response. The Opaque value identifies this route in the signed challenge; use a different value for another paid operation.

Use the Node.js SDK with the mppx/express adapter. Here, app is your Express application, and apiKey and secretKey come from private configuration.

import { Mppx } from 'mppx/express';
import { inflow, paymentHttpTransport } from '@inflowpayai/mpp-seller';

const payments = Mppx.create({
  methods: [inflow({ apiKey, environment: 'sandbox' })],
  secretKey,
  transport: paymentHttpTransport(),
});

app.get('/api/report',
  payments.charge({ amount: '0.01', currency: 'USDC' }),
  (_request, response) => {
    response.set('Cache-Control', 'no-store');
    response.json({ report: 'Your sample report is ready.' });
  },
);

Keep paymentHttpTransport() in the configuration. It returns server failures without adding a fresh payment challenge. The Express adapter runs the paid handler after accepting payment and attaches the receipt when this handler calls response.json.

This example returns JSON. Do not assume response.send, file downloads, or streaming use the same receipt hook. Use a response integration that preserves Payment-Receipt for those response types.

Use the Python SDK with FastAPI and pympp. Here, seller is the open Seller client, app is your FastAPI application, and secret is the signing secret.

terms = seller.charge_request({"amount": "0.01", "currency": "USDC"})

@app.get("/api/report")
@pay(
    intent=seller, method=seller.method, request=terms,
    realm="report-service", secret_key=secret,
)
async def report(
    request: Request, credential: Credential, receipt: Receipt,
) -> JSONResponse:
    return JSONResponse(
        {"report": "Your sample report is ready."},
        headers={
            "Payment-Receipt": receipt.to_payment_receipt(),
            "Cache-Control": "no-store",
        },
    )

Keep the Seller client open for the application’s lifetime. The @pay decorator accepts payment before calling report. The handler explicitly includes the supplied receipt in its response. Retain the imports and client lifecycle from the quickstart.

Use the Rust SDK with the explicit Axum handler from the quickstart. That handler returns an unpaid challenge, reads the payment header, and decodes the credential. Its paid-response portion is:

let receipt = offer
    .accept(&credential, None, &CancellationToken::new())
    .await?;
let receipt = inflow_mpp::encode(&serde_json::to_value(receipt)?)?;

Ok((
    [
        ("payment-receipt", receipt),
        ("cache-control", "no-store".to_owned()),
    ],
    Json(json!({"report":"Your sample report is ready."})),
)
    .into_response())

offer contains your server’s payment terms; credential is the decoded buyer submission. accept checks the credential against those terms and accepts payment. Only its successful receipt permits execution to reach the result.

Keep the surrounding challenge, malformed-header, and error handling from the complete quickstart. This excerpt belongs inside its paid_report function, which returns errors to the outer handler; it is not a complete route. None means this GET example does not supply request-body bytes for digest verification.

These examples have no application login and use Authorization: Payment for the payment credential. Payment approval is not a substitute for checking who may access your application’s data.

If your API already uses the Authorization header for login, resolve that header integration before adding payments. Do not overwrite the login credential or treat a payment credential as a login. Support for a separate payment header depends on the selected SDK and adapter; copying these payment-only fragments does not establish that integration.

Handle request data

A fixed-price GET route can use the same payment terms for each request. For a POST operation, first parse and validate the requested product, quantity, or other inputs. Calculate the price from your own product data rather than accepting a price supplied by the buyer.

The buyer retries the request with a payment credential. Your application must check that the credential pays for the operation being requested. Matching an amount alone does not distinguish two products with the same price.

  • Keep the route, selected product, quantity, and applicable payment terms consistent between the challenge and the paid request.
  • When payment is bound to a request-body digest, preserve the original body bytes for verification. Parsing and re-serializing JSON can produce different bytes.
  • Arrange request-body reading so both the payment integration and your handler can use the required data. Do not consume a request stream and leave the handler with an empty body.

Body verification depends on the integration you select:

  • Rust’s offer.challenge(body) and offer.accept(..., body, ...) accept body bytes explicitly.
  • Python’s @pay has an optional body argument for digest verification.
  • The Node.js Express adapter shown here passes the method, URL, and headers to its payment handler, without the body.
  • Go’s Protect route does not expose a body-digest option.

The fixed-price GET examples therefore do not demonstrate body-bound POST payments. Verify the selected adapter’s request binding before extending them to a request-dependent purchase. Keep operation-specific validation in the application, even when the payment layer verifies a signature or digest.

Return the result and receipt

  • Unpaid request. Return 402 Payment Required with the WWW-Authenticate: Payment challenge. Let the SDK generate and sign the challenge.
  • Submitted payment. Read the buyer’s payment credential through the selected integration. A parseable credential is not proof of payment. The seller must check and accept it before delivering the paid result.
  • Accepted payment. Return the result with Payment-Receipt. Go adds this header, the Node.js example uses its JSON response hook, and the Python and Rust examples add it explicitly.
  • Failed or uncertain payment. Do not return the protected result. Preserve the integration’s error response and distinguish a request for payment from a failure to confirm an attempted payment.

Do not convert every payment error into a fresh challenge. After a timeout or uncertain outcome, a new payment attempt can charge again without resolving the first attempt. See Status and recovery for recovery decisions.

Keep payment headers intact through your application middleware and reverse proxy. Do not let a shared cache serve a paid result to another caller. The snippets set Cache-Control: no-store on their successful report responses.

Payment and delivery are separate operations. The payment can succeed before report generation or response delivery fails. A receipt does not automatically refund that payment or make your handler run only once. Use Deliver paid results to plan safe retries and delivery recovery.

Check the integration

Use controlled Sandbox accounts and a known price. Confirm the buyer’s funds and the selected payment environment before running a paid test. Use the quickstart’s purchase steps against your endpoint, then test these outcomes:

  • Without a payment credential, the endpoint returns a challenge and does not execute the paid work. An unrelated free route still works.
  • A successful payment returns the expected result and a payment receipt. Confirm the corresponding transaction in the seller’s dashboard.
  • A malformed or invalid payment credential does not reach the paid work.
  • An invalid application request, such as an unavailable product or an invalid quantity, is rejected before accepting payment.
  • A simulated payment-service failure does not deliver the paid result. Check that your application preserves the error instead of treating it as payment success.

Keep these integration checks alongside your project’s unit tests, coverage, and static analysis. Record the cases actually tested; receiving a challenge alone does not prove that payment and delivery work. For tests that submit payment, agree on the destination and spending limit first.

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.