Skip to content

Agent integration (pr402 facilitator)

New here? Start with the concise quick starts:

Runbook for two kinds of autonomous clients:

You are…Jump to
Seller / resource provider / merchant (you publish APIs or resources and receive payment)Seller agents
Buyer / payer agent (you call paid resources and settle via x402)Buyer agents
I have a 402 accepts[] line from a seller — what now?Payment pipeline · pr402 vs spec

x402 v2 Header Convention (per HTTP transport spec):

HeaderDirectionDescription
PAYMENT-REQUIREDServer → ClientBase64-encoded PaymentRequired JSON (on HTTP 402)
PAYMENT-SIGNATUREClient → ServerPayment proof (raw JSON or base64) — replaces V1 X-PAYMENT
PAYMENT-RESPONSEServer → ClientBase64-encoded settlement result (on HTTP 200 or 402 after settle)

Canonical contract: OpenAPI 3.1 at GET /openapi.json on your facilitator base URL.

Status. pr402 is live on Solana Mainnet and Devnet; the exact rail is GA, and sla-escrow is available to integrators who operate or trust a production oracle_authority (reference: the oracles/ workspace ships three sibling oracle profiles — api-quality, onchain-transfer, file-delivery). Behavior and flags can evolve — GET /capabilities and GET /openapi.json on the host you actually call are the live contract.

RecommendedAlso available (same service)
Production (Mainnet)https://ipay.shhttps://agent.pay402.me
Preview (Devnet)https://preview.ipay.shhttps://preview.agent.pay402.me

Confirm solanaNetwork, chainId, and feature flags with GET /api/v1/facilitator/health or GET /api/v1/facilitator/capabilities on the host you actually call. Match the origin your seller documents; do not assume preview.

Wallet RPC: Read solanaWalletRpcUrl from GET /health when you need the deployment’s wallet-facing RPC. Do not hardcode RPC URLs from documentation.

Golden path (exact scheme) — HTTP 402 buyer checklist

Use this order so you do not mismatch facilitator hosts or JSON shapes:

  1. Facilitator URL — Same origin your seller documents. Production: https://ipay.sh · Preview: https://preview.ipay.sh (also agent.pay402.me / preview.agent.pay402.me). Confirm solanaNetwork with GET /health.
  2. GET /api/v1/facilitator/supported (or /capabilities) — Confirm exact / v2:solana:exact is listed.
  3. Receive HTTP 402 from the seller — Save paymentRequirements and the chosen accepts[] line.
  4. POST /api/v1/facilitator/build-exact-payment-tx — Body: { "payer": "<buyer pubkey>", "accepted": <accepts[] line>, "resource": <from 402> }. Response: transaction, verifyBodyTemplate, payerSignatureIndex, …
  5. Sign — Sign transaction at payerSignatureIndex. Put signed base64 into verifyBodyTemplate.paymentPayload.payload.transaction.
  6. Retry seller — Send the filled verifyBodyTemplate in header PAYMENT-SIGNATURE (raw JSON or base64). Do not call facilitator /verify or /settle first when using SDK, MCP, or x402-buyer-starter.
  7. Seller settles — Seller gate calls facilitator /settle (verify runs internally). Success: HTTP 200 + optional PAYMENT-RESPONSE.

Advanced: buyer-side POST /verify then POST /settle before step 6 — debugging only; see Buyer agents.

Scheme strings (402 vs verify/settle): Sellers often publish v2:solana:exact or v2:solana:sla-escrow in HTTP 402 accepts[], matching GET /supported / discovery. POST .../build-*-payment-tx accepts those aliases (or wire exact / sla-escrow) on the request’s accepted object, but the returned verifyBodyTemplate normalizes scheme to the x402 wire values exact or sla-escrow in both paymentPayload.accepted and paymentRequirements. POST /verify and POST /settle also accept either wire or v2:solana:* on v2 bodies and normalize before verification, so older cached proofs stay valid.

Shape reminder (after build): The object you POST to /verify and /settle matches verifyBodyTemplate from the build response (with signed tx). It has top-level x402Version, paymentPayload (includes nested accepted, payload.transaction, resource), and paymentRequirements — see OpenAPI schema X402V2VerifySettleBody and its example on GET /openapi.json.

SLA-Escrow — Use POST /build-sla-escrow-payment-tx instead of step 4; include slaHash and oracleAuthority per OpenAPI. Do not use the exact builder for escrow lines.


Important (pr402 ≠ simple wallet payTo): This facilitator settles through UniversalSettle (v2:solana:exact) and/or SLA-Escrow (v2:solana:sla-escrow). Your 402 payTo (and matching proof destinations) must be the on-chain PDA your buyers pay into—not a bare seller wallet for settlement proofs:

  • exact: use the UniversalSettle split-vault / rail PDAs from seller discovery (GET /api/v1/facilitator/sellers/{wallet}/rails/exact) or your integrator’s docs.
  • sla-escrow: payTo must be the Escrow PDA for the asset mint and facilitator bank (the facilitator verifies this). The POST /build-sla-escrow-payment-tx response verifyBodyTemplate sets the canonical payTo for you.

Seller agents (resource providers)

Seller endpoint decision matrix

Do not collapse these into one endpoint — each has a distinct side effect. Use this table (also available as sellerEndpointGuide on GET /capabilities) to pick the right call:

GoalMethod + pathSide effectWhen to use
Multi-rail preview + lifecycle ladderGET /api/v1/facilitator/sellers/{wallet}/previewRead-onlyFirst seller call; inspect lifecycle.nextStep
Single-rail payTo lookupGET /api/v1/facilitator/sellers/{wallet}/rails/{scheme} (+ ?asset= for sla-escrow)Read-onlyBuilding one accepts[] line
Activate — unsigned CreateVault txPOST /api/v1/facilitator/sellers/provision-txReturns unsigned txlifecycle.nextStep === "activate"
Enrich naive 402 bodyPOST /api/v1/facilitator/payment-required/enrichRead-only JSON transformPublishing HTTP 402
Registry challengeGET /api/v1/facilitator/sellers/{wallet}/challengeRead-onlyOptional public directory
Registry submitPOST /api/v1/facilitator/sellers/{wallet}/registerDB write when configuredAfter Activate on-chain

Seller lifecycle: Preview → Activate → Verify

The facilitator exposes a three-stage seller lifecycle. Each stage has a distinct side effect; the stages are surfaced machine-readably via the lifecycle block on GET /api/v1/facilitator/sellers/{wallet}/preview (previewed / activated / verified / nextStep).

StageHTTPSide effectRequired?
PreviewGET /api/v1/facilitator/sellers/{wallet}/previewNone. Derives vault PDAs and returns on-chain state.No wallet needed.
ActivatePOST /api/v1/facilitator/sellers/provision-txReturns an unsigned CreateVault (+ optional ATA) tx. After the seller signs and broadcasts, the on-chain SplitVault exists and unlocks the sovereign 90 bps fee tier.Required before accepting payments.
Verify (optional)GET /api/v1/facilitator/sellers/{wallet}/challenge then POST /api/v1/facilitator/sellers/{wallet}/registerWrites a verified row in the off-chain resource_providers registry so the seller appears in discovery. The facilitator refuses this step with 409 Conflict until Activate has landed.Optional. Only required for verified-seller discovery listings.

The POST /sellers/provision-tx response carries a statusCode enum so agents don't have to parse notes[]:

  • ALREADY_PROVISIONED — vault (and ATA if applicable) already exist; no transaction returned.
  • VAULT_AND_ATA — first-time SPL provisioning (two setup ixs).
  • VAULT_ONLY — first-time native-SOL provisioning (single CreateVault ix).
  • ATA_ONLY — vault exists, adding a new SPL mint (single ATA-create ix).

If you receive payment for resources and want Sovereign status (90 bps fee tier — a 10 bps discount off the standard 100 bps rate) and correct 402 accepts[] lines:

  1. Discover rules: Onboarding guide — Sovereign vs facilitated (JIT) paths.
  2. Preview (no wallet): GET /api/v1/facilitator/sellers/{YOUR_PUBKEY}/preview. The lifecycle block tells you what stage to act on next.
  3. Activate (on-chain):
    • Build: POST /api/v1/facilitator/sellers/provision-tx with { "wallet": "<YOUR_PUBKEY>", "asset": "SOL" } (or USDC, USDT, or a base58 SPL mint). Idempotent per (wallet, asset) (statusCode: "ALREADY_PROVISIONED" + no transaction when done).
    • Sign the base64 bincode VersionedTransaction with your seller key.
    • Send to Solana. Signing with the seller wallet itself earns the ongoing 10 bps protocol fee discount (90 bps sovereign rate vs 100 bps standard).
  4. Discover your payTo (vault PDA) and metadata:
    bash
    curl -sS "https://<facilitator-url>/api/v1/facilitator/sellers/{YOUR_PUBKEY}/rails/exact" | jq .
    Try the Vault Explorer on the facilitator / landing page for the same resolution.
  5. Verify identity (optional): GET /api/v1/facilitator/sellers/{wallet}/challenge, sign the returned message with the wallet, then POST /api/v1/facilitator/sellers/{wallet}/register with { wallet, message, signature, asset }.
  6. Publishing x402: See Publishing a Payment Required line so your accepts[] matches what this facilitator verifies.

Publishing a Payment Required line (sellers)

Your HTTP 402 body must be valid x402 v2, but fields must match this facilitator’s on-chain programs—not a generic “transfer to my wallet” mental model.

  1. Tell buyers which facilitator hosts settle
    Your docs or API should state the facilitator base URL buyers must call for verify / settle / build endpoints (same host that serves /capabilities). If you operate a white-label stack, document the exact origin.

  2. Bootstrap shape from discovery
    Call GET /api/v1/facilitator/supported (or read supported inside GET /capabilities). Copy the structure of a kinds[] entry for your rail (v2:solana:exact or v2:solana:sla-escrow): network, scheme, and especially extra (fee payer, program IDs, oracle lists, bank/config PDAs). Your accepts[] lines should be consistent with that shape so buyers can call builders without guessing.

    Several options in one 402: x402 accepts[] is an array—each entry is a full payment requirement with its own payTo, asset, and metadata; the buyer returns one chosen line as accepted. This is how you advertise more than one token or rail on the same resource. With this facilitator’s one asset per merchant wallet rule, use distinct seller pubkeys per rail and give each accepts[] row the payTo / extra.merchantWallet from discovery for that key (see Onboarding guidePolicy: one payment asset per merchant wallet).

  3. v2:solana:exact (UniversalSettle)

    • payTo: Must identify the vault rail the facilitator checks on-chain (split-vault / SOL storage / vault ATA semantics per deployment). Use PDAs from GET /api/v1/facilitator/sellers/{your_seller_pubkey}/rails/exact (vaultPda, solStoragePda, etc.). Do not publish only your personal wallet as payTo unless that is explicitly the derived rail for your deployment.
    • extra: Should align with the supported kind (e.g. feePayer, programId, configAddress, merchantWallet, beneficiary as your product uses them). Buyers’ proofs are checked against paymentRequirements and the wire transaction.
  4. v2:solana:sla-escrow

    • payTo: Must be the Escrow PDA for asset mint + facilitator bank (same derivation the facilitator uses). If in doubt, buyers can rely on POST /build-sla-escrow-payment-tx, which injects canonical payTo into verifyBodyTemplate.
    • extra: Must include bankAddress, escrowProgramId, oracleAuthorities, and related fields consistent with supported for sla-escrow. extra.bankAddress must match the facilitator’s configured bank.
    • Optional but recommended: merchantWallet in extra for seller identity in metadata (and for your own dashboards).
  5. Operational constraints your buyers will hit — payment mint allowlist
    Deployments may set PR402_ALLOWED_PAYMENT_MINTS (comma / whitespace‑separated base58 mints; env or parameters table — include 11111111111111111111111111111111 if native SOL lines must pass).

    • Non-empty list: exact and sla-escrow /verify, /settle, build-exact-payment-tx, and build-sla-escrow-payment-tx reject any accepted.asset / paymentRequirements.asset not in the list (same error text as verify).
    • Empty / unset: permissive (all mints). The facilitator logs a one-time warning at first check in that mode — do not rely on this in production.
    • POST /payment-required/enrich: when an allowlist is configured, the server warns (non-blocking) if any accepts[].asset is missing from the list so you can fix 402 bodies before buyers hit hard failures.
  6. Minimal mental model
    You are not only “pasting Coinbase x402 examples”; you are pinning your resource to this facilitator’s Solana rails. When in doubt, reproduce a happy path with build-exact-payment-tx / build-sla-escrow-payment-tx locally, then mirror the accepted object in your live accepts[].

Seller agent checklist (automation)

  1. Capabilities: GET /api/v1/facilitator/capabilities — confirm features.universalSettleExact, features.unsignedExactPaymentTxBuild, and (if you sell via escrow) features.slaEscrow / features.unsignedSlaEscrowPaymentTxBuild. Seller lifecycle endpoints are under httpEndpoints.sellerPreview / sellerChallenge / sellerRegister / sellerProvisionTx.
  2. Preview: GET /api/v1/facilitator/sellers/{PUBKEY}/preview. Inspect the lifecycle block — if nextStep === "activate", skip to step 4. If isSovereign: true on schemes.exact, you already have the discount path.
  3. Scheme discovery: GET /api/v1/facilitator/sellers/{PUBKEY}/rails/exact (or /sellers/{PUBKEY}/rails/sla-escrow?asset=<MINT>) for a single canonical payTo.
  4. Activate: POST /api/v1/facilitator/sellers/provision-tx with wallet + asset for that seller key's single rail. Inspect statusCode: ALREADY_PROVISIONED means no tx to sign; otherwise sign the base64 bincode tx and broadcast.
  5. Verify (optional): GET /api/v1/facilitator/sellers/{PUBKEY}/challenge → sign the returned messagePOST /api/v1/facilitator/sellers/{PUBKEY}/register with { message, signature (base58), asset } (path wallet is canonical; body wallet, if present, must equal the path). Expect 409 Conflict if Activate hasn't landed on-chain yet.
  6. Balances (debug): GET /api/v1/facilitator/vault-snapshot?wallet=<PUBKEY> (UniversalSettle deployments).

Buyer agents (payers)

Fastest path. npm i -g @pr402/client && pr402-buy --resource <URL> --payer <keypair.json> --mint <MINT> (or cargo install pr402-client). Uses X402AgentClient.fetchWithAutoPay: 402 → build → sign → PAYMENT-SIGNATURE retry. The seller settles; the buyer does not call /verify or /settle first. MCP: @pr402/mcp-server · starter: x402-buyer-starter.

Discover sellers. GET /api/v1/facilitator/providers returns the public directory of verified, opted-in sellers (paginated via ?limit=&cursor=). Each entry carries serviceUrl, tags[], displayName, and the settlement rail pubkeys — enough to build an accepts[] line without a prior 402. Single-wallet lookup: GET /api/v1/facilitator/providers/{wallet}. The facilitator verifies wallet control only; it does not vet the advertised service.

How pr402 differs from the generic x402 spec

The x402 v2 spec describes HTTP 402, accepts[], payloads, and facilitators in the abstract. pr402 is a concrete Solana facilitator with extra rules you must satisfy:

TopicGeneric spec expectationpr402 reality
payToOften documented as “who gets paid” / recipient identityFor exact and sla-escrow, settlement is to on-chain PDAs (UniversalSettle vault rail or SLA Escrow account), not necessarily a simple wallet transfer layout.
Transaction shapeWallet or app builds something that proves paymentProofs are specific Solana transactions: compute-budget layout, optional ATA create, TransferChecked or SLA-Escrow FundPayment, fee-payer rules, no facilitator fee payer inside instruction account metas (exact path).
Building the txImplementation-definedOptional: POST /build-exact-payment-tx or POST /build-sla-escrow-payment-tx return bincode VersionedTransaction shells + verifyBodyTemplate.
Versioned tx featuresNot specifiedTransactions that use address lookup tables (loaded addresses) are rejected until explicitly supported; use static-account-key shells only.
Who signsVariesOften two signers when the facilitator pays Solana fees (fee payer + payer authority); see build response notes and OpenAPI.
Facilitator URLMay be impliedYou must call the same deployment the seller used to define rails (check seller docs or capabilities on that host).
Payment mint allowlistNot in the abstract specIf configured, your accepted.asset must be listed or build-* / /verify / /settle fail early with an explicit “not supported … Approved assets: …” message.

If verification fails with recipient / asset / amount errors, the usual cause is accepts[] not matching the PDA layout this facilitator checks—not a bug in your wallet.


Payment pipeline: from accepts[] to settlement

Default path when calling an HTTP 402 seller (matches @pr402/client, MCP, x402-buyer-starter):

  1. Read accepts[] — pick one line for your wallet, chain, and asset.
  2. Confirm facilitator — seller-documented base URL (must match escrow extra for sla-escrow).
  3. GET /capabilities — confirm build features for your scheme.
  4. BuildPOST /build-exact-payment-tx or /build-sla-escrow-payment-tx with payer, accepted, and scheme fields per OpenAPI.
  5. Sign — sign transaction at payerSignatureIndex. Do not add address lookup tables.
  6. Fill template — put signed tx into verifyBodyTemplate.paymentPayload.payload.transaction. Keep paymentPayload.accepted and paymentRequirements identical.
  7. Retry seller — header PAYMENT-SIGNATURE (raw JSON preferred; base64 accepted).
  8. Seller settles — seller calls facilitator /settle. Read PAYMENT-RESPONSE on 200 for on-chain proof.

Blockhash expiry: rebuild from step 4 and re-sign.

Advanced — buyer-side verify/settle: POST /verify then POST /settle with the same body before step 7. Use for debugging only; not the SDK/starter default. Reuse correlationId / X-Correlation-ID when auditing.


1. Discover

bash
# Use the same origin your seller documents: production or preview (see table above).
BASE="https://ipay.sh"   # preview: https://preview.ipay.sh — or agent.pay402.me / preview.agent.pay402.me (same APIs)
curl -sS "$BASE/api/v1/facilitator/supported" | jq .
# or
curl -sS "$BASE/api/v1/facilitator/capabilities" | jq .

Pick one kinds[] entry that matches the 402 accepts[] line your resource returned (scheme, network, asset, amount, payTo, extra). Align payTo with seller-published PDAs (see Seller agents).


2. Build unsigned tx

Two build endpoints; do not confuse them.

accepts[].schemeEndpointWho signs before verify
exactPOST /api/v1/facilitator/build-exact-payment-txPayer signs token authority; facilitator is fee payer at settle (default).
sla-escrowPOST /api/v1/facilitator/build-sla-escrow-payment-txBuyer partial sign; facilitator completes fee payer at settle (default).

Request bodies are in openapi.json (BuildExactPaymentTxRequest, BuildSlaEscrowPaymentTxRequest). Response includes verifyBodyTemplate and base64 transaction (bincode VersionedTransaction, unsigned). For sla-escrow, trust the template’s payTo (canonical Escrow PDA) even if your upstream 402 line still showed a legacy value.


3. Sign locally

Sign at payerSignatureIndex. Replace paymentPayload.payload.transaction in verifyBodyTemplate with the signed base64. Keep accepted identical to paymentRequirements.


4. Retry with PAYMENT-SIGNATURE

Retry the seller request with the filled verifyBodyTemplate in header PAYMENT-SIGNATURE. The seller gate calls facilitator /settle.


Advanced: buyer-side verify and settle

Not the default for pr402-buy, MCP, or x402-buyer-starter. Same JSON body for both:

bash
curl -sS -X POST "$BASE/api/v1/facilitator/verify" \
  -H "Content-Type: application/json" \
  -d @verify-body.json | jq .

curl -sS -X POST "$BASE/api/v1/facilitator/settle" \
  -H "Content-Type: application/json" \
  -d @verify-body.json | jq .

If the seller still requires proof, retry with PAYMENT-SIGNATURE as in step 4.


Scheme naming

pr402 uses short canonical names on the wire:

Canonical (use this)Qualified alias (also accepted)Description
exactv2:solana:exactUniversalSettle instant settlement
sla-escrowv2:solana:sla-escrowSLA-Escrow time-bound settlement

In accepts[], paymentRequirements.scheme, and builder request accepted.scheme, use the canonical name. The qualified forms are accepted for backward compatibility.


Design highlights (what makes pr402 different)

These are deliberate design choices that differentiate pr402 from a generic x402 facilitator:

FeatureBenefit
verifyBodyTemplateBuild endpoints return a ready-to-use verify/settle body template. Buyers just sign and slot the tx in — no manual JSON construction, no mismatched fields.
Idempotent /settleIf the transaction is already confirmed on-chain, settle returns success. Safe for retries, agent loops, and network interruptions.
/payment-required/enrich (Lite → Full 402)Sellers can post a naive 402 body with bare wallet payTo and receive back a fully institutional response with PDA-derived addresses and extra metadata. Eliminates PDA math on the seller side.
Dual scheme supportBoth exact (instant UniversalSettle) and sla-escrow (time-bound escrow with oracle adjudication) are supported from a single facilitator deployment.
/sellers/{wallet}/rails/{scheme} (lightweight)Single-scheme, read-only lookup of payTo PDA. No auth, no DB. Sellers can call this from any language with a simple HTTP GET.
CORS Access-Control-Expose-HeadersPAYMENT-RESPONSE, X-Correlation-ID, and X-API-Version are exposed so browser-based agents can read settlement results.
Toxic asset protectionConfigurable mint allowlist (PR402_ALLOWED_PAYMENT_MINTS) prevents settlement with worthless spam tokens.
Seller quick startLanguage-agnostic guide at /seller-quick-start with pseudocode and examples in Rust, Python, JS/TS, and Go.

Technical specs

  • x402 v2 (protocol): Specification
  • Facilitator — machine-readable: {FACILITATOR}/openapi.json (OpenAPI 3.1) — primary contract for agents and codegen.
  • Facilitator — Markdown mirror: {FACILITATOR}/agent-integration.md (same runbook as this page when deployments stay aligned).
  • Facilitator — other Markdown artifacts: e.g. {FACILITATOR}/seller-quick-start.md, {FACILITATOR}/onboarding_guide.md (see deployment routing).
  • Humans — docs site: API overview · Seller Quick Start · Buyer Quick Start.

Internal ops: cron sweep (private)

POST /api/v1/facilitator/sweep is an internal-only endpoint for scheduler/cron execution.

  • Auth: Authorization: Bearer <token>
  • Token source: PR402_SWEEP_CRON_TOKEN (parameters table takes precedence over env var).
  • Purpose: Drain eligible UniversalSettle vault balances without requiring a settlement request in the same invocation.
  • Safety: Use {"dryRun": true} first in cron rollout.

Vercel Cron note

Vercel Cron sends GET requests. Use:

  • GET /api/v1/facilitator/sweep-cron (private, bearer-auth)

This route runs the same sweep engine with configured defaults (equivalent to an empty POST body).

Sweep parameters (DB parameters keys)

DB values override env values in this project.

  • PR402_SWEEP_CRON_TOKEN
    • Bearer token for the private sweep endpoint.
    • Seeded in migrations/init.sql as bootstrap placeholder (CHANGE_ME_BEFORE_PRODUCTION).
  • PR402_SWEEP_CRON_COOLDOWN_SEC (default: 300)
    • Minimum interval between sweep attempts per provider rail.
  • PR402_SWEEP_CRON_RECENT_SETTLE_WINDOW_SEC (default: 86400)
    • Candidate must have a successful settle within this recent window.
  • PR402_SWEEP_CRON_BATCH_LIMIT (default: 50)
    • Maximum candidate rails processed per sweep run.
  • PR402_SWEEP_MIN_SPENDABLE_LAMPORTS (default: 30000000)
    • SOL threshold (0.03 SOL) before attempting sweep.
  • PR402_SWEEP_MIN_SPL_RAW_DEFAULT (default: 3000000)
    • Default SPL raw threshold when mint has no explicit override.
  • PR402_SWEEP_MIN_SPL_RAW_BY_MINT
    • JSON map for per-mint SPL raw thresholds.

Suggested scheduler body

json
{
  "dryRun": false,
  "limit": 50,
  "cooldownSeconds": 300,
  "requireRecentSettleWithinSeconds": 86400
}

Built for the autonomous future.