Agent integration (pr402 facilitator)
New here? Start with the concise quick starts:
- Buyers:
/quickstart-buyer— 6 steps, copy-paste curl commands- Sellers:
/quickstart-seller— 5 steps, no PDA math needed
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):
Header Direction Description PAYMENT-REQUIREDServer → Client Base64-encoded PaymentRequiredJSON (on HTTP 402)PAYMENT-SIGNATUREClient → Server Payment proof (raw JSON or base64) — replaces V1 X-PAYMENTPAYMENT-RESPONSEServer → Client Base64-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
exactrail is GA, andsla-escrowis available to integrators who operate or trust a productionoracle_authority(reference:oracle-qa). Behavior and flags can evolve —GET /capabilitiesandGET /openapi.jsonon the host you actually call are the live contract.
| Recommended | Also available (same service) | |
|---|---|---|
| Production (Mainnet) | https://ipay.sh | https://agent.pay402.me |
| Preview (Devnet) | https://preview.ipay.sh | https://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) — standard integration checklist
Use this order so you do not mismatch facilitator hosts or JSON shapes:
- Facilitator URL — Same origin your seller documents (or embedded in discovery). Recommended hosts: Production
https://ipay.sh, Previewhttps://preview.ipay.sh. Also served (not deprecated):https://agent.pay402.me,https://preview.agent.pay402.me. ConfirmsolanaNetworkwithGET /healthon that host. GET /api/v1/facilitator/supported(or/capabilities) — Confirmexact/v2:solana:exactis listed.- Receive HTTP 402 from the seller — Save
paymentRequirementsand the chosenaccepts[]line. POST /api/v1/facilitator/build-exact-payment-tx— Body:{ "payer": "<buyer pubkey>", "accepted": <same object as the accepts[] line>, "resource": <from 402> }. Response is camelCase JSON (transaction,verifyBodyTemplate,payerSignatureIndex, …).- Sign — Deserialize
transaction(base64 → bincode →VersionedTransaction), sign atpayerSignatureIndex, re-encode base64, put intoverifyBodyTemplate.paymentPayload.payload.transaction(replace the unsigned value). POST /verifythenPOST /settle— Send the filledverifyBodyTemplateJSON as the body (same object for both). Optional: reusecorrelationId/X-Correlation-IDfrom a successful verify when calling settle.
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/discovery?wallet=…&scheme=exact) or your integrator’s docs.sla-escrow:payTomust be the Escrow PDA for the asset mint and facilitator bank (the facilitator verifies this). ThePOST /build-sla-escrow-payment-txresponseverifyBodyTemplatesets the canonicalpayTofor you.
Seller agents (resource providers)
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/onboard (previewed / activated / verified / nextStep).
| Stage | HTTP | Side effect | Required? |
|---|---|---|---|
| Preview | GET /api/v1/facilitator/onboard?wallet=… | None. Derives vault PDAs and returns on-chain state. | No wallet needed. |
| Activate | POST /api/v1/facilitator/onboard/provision | Returns 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/onboard/challenge then POST /api/v1/facilitator/onboard | Writes 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 /onboard/provision response carries a statusCode enum so agents don't have to parse notes[]:
ALREADY_PROVISIONED— vault (and ATA if applicable) already exist; notransactionreturned.VAULT_AND_ATA— first-time SPL provisioning (two setup ixs).VAULT_ONLY— first-time native-SOL provisioning (singleCreateVaultix).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:
- Discover rules: Onboarding guide — Sovereign vs facilitated (JIT) paths.
- Preview (no wallet):
GET /api/v1/facilitator/onboard?wallet=<YOUR_PUBKEY>. Thelifecycleblock tells you what stage to act on next. - Activate (on-chain):
- Build:
POST /api/v1/facilitator/onboard/provisionwith{ "wallet": "<YOUR_PUBKEY>", "asset": "SOL" }(orUSDC,USDT, or a base58 SPL mint). Idempotent per(wallet, asset)(statusCode: "ALREADY_PROVISIONED"+ notransactionwhen done). - Sign the base64 bincode
VersionedTransactionwith 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).
- Build:
- Discover your
payTo(vault PDA) and metadata:bashTry the Vault Explorer on the facilitatorcurl -sS "https://<facilitator-url>/api/v1/facilitator/discovery?wallet=<YOUR_PUBKEY>&scheme=exact" | jq ./landing page for the same resolution. - Verify identity (optional):
GET /api/v1/facilitator/onboard/challenge?wallet=…, sign the returnedmessagewith the wallet, thenPOST /api/v1/facilitator/onboardwith{ wallet, message, signature, asset }. The signature must be base58-encoded Ed25519. The facilitator returns409 Conflictif the on-chain vault does not yet exist — run Activate first. RequiresDATABASE_URL+ HMAC secret on the server; persists verified vault metadata for discovery. - 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.
Tell buyers which facilitator hosts settle
Your docs or API should state the facilitator base URL buyers must call forverify/settle/ build endpoints (same host that serves/capabilities). If you operate a white-label stack, document the exact origin.Bootstrap shape from discovery
CallGET /api/v1/facilitator/supported(or readsupportedinsideGET /capabilities). Copy the structure of akinds[]entry for your rail (v2:solana:exactorv2:solana:sla-escrow):network,scheme, and especiallyextra(fee payer, program IDs, oracle lists, bank/config PDAs). Youraccepts[]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 ownpayTo,asset, and metadata; the buyer returns one chosen line asaccepted. 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 eachaccepts[]row thepayTo/extra.merchantWalletfrom discovery for that key (see Onboarding guide — Policy: one payment asset per merchant wallet).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 fromGET /api/v1/facilitator/discovery?wallet=<your_seller_pubkey>&scheme=exact(vaultPda,solStoragePda, etc.). Do not publish only your personal wallet aspayTounless that is explicitly the derived rail for your deployment.extra: Should align with thesupportedkind (e.g.feePayer,programId,configAddress,merchantWallet,beneficiaryas your product uses them). Buyers’ proofs are checked againstpaymentRequirementsand the wire transaction.
v2:solana:sla-escrowpayTo: Must be the Escrow PDA forassetmint + facilitator bank (same derivation the facilitator uses). If in doubt, buyers can rely onPOST /build-sla-escrow-payment-tx, which injects canonicalpayTointoverifyBodyTemplate.extra: Must includebankAddress,escrowProgramId,oracleAuthorities, and related fields consistent withsupportedforsla-escrow.extra.bankAddressmust match the facilitator’s configured bank.- Optional but recommended:
merchantWalletinextrafor seller identity in metadata (and for your own dashboards).
Operational constraints your buyers will hit — payment mint allowlist
Deployments may setPR402_ALLOWED_PAYMENT_MINTS(comma / whitespace‑separated base58 mints; env orparameterstable — include11111111111111111111111111111111if native SOL lines must pass).- Non-empty list:
exactandsla-escrow/verify,/settle,build-exact-payment-tx, andbuild-sla-escrow-payment-txreject anyaccepted.asset/paymentRequirements.assetnot 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 /upgrade: when an allowlist is configured, the server warns (non-blocking) if anyaccepts[].assetis missing from the list so you can fix 402 bodies before buyers hit hard failures.
- Non-empty list:
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 withbuild-exact-payment-tx/build-sla-escrow-payment-txlocally, then mirror theacceptedobject in your liveaccepts[].
Seller agent checklist (automation)
- Capabilities:
GET /api/v1/facilitator/capabilities— confirmfeatures.universalSettleExact,features.unsignedExactPaymentTxBuild, and (if you sell via escrow)features.slaEscrow/features.unsignedSlaEscrowPaymentTxBuild. Seller lifecycle endpoints are underhttpEndpoints.onboardPreview/onboardChallenge/onboard/onboardProvision. - Preview:
GET /api/v1/facilitator/onboard?wallet=<PUBKEY>. Inspect thelifecycleblock — ifnextStep === "activate", skip to step 4. IfisSovereign: trueonschemes.exact, you already have the discount path. - Scheme discovery:
GET /api/v1/facilitator/discovery?wallet=<PUBKEY>&scheme=exact(orsla-escrow&asset=<MINT>) for a single canonicalpayTo. - Activate:
POST /api/v1/facilitator/onboard/provisionwithwallet+assetfor that seller key's single rail. InspectstatusCode:ALREADY_PROVISIONEDmeans no tx to sign; otherwise sign the base64 bincode tx and broadcast. - Verify (optional):
GET /api/v1/facilitator/onboard/challenge?wallet=<PUBKEY>→ sign the returnedmessage→POST /api/v1/facilitator/onboardwith{ wallet, message, signature (base58), asset }. Expect409 Conflictif Activate hasn't landed on-chain yet. - Balances (debug):
GET /api/v1/facilitator/vault-snapshot?wallet=<PUBKEY>(UniversalSettle deployments).
Buyer agents (payers)
Fastest path. Install the SDK and call one command:
npm i -g @pr402/client && pr402-buy --resource <URL> --payer <keypair.json> --mint <MINT>orcargo install pr402-clientfor the Rust binary. Both shippr402-buy, and both also expose a library (X402AgentClient) for embedding. The sections below document the protocol underneath — read them when implementing from scratch, debugging the CLI, or integrating in a language without a published SDK.
Discover sellers.
GET /api/v1/facilitator/providersreturns the public directory of verified, opted-in sellers (paginated via?limit=&cursor=). Each entry carriesserviceUrl,tags[],displayName, and the settlement rail pubkeys — enough to build anaccepts[]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:
| Topic | Generic spec expectation | pr402 reality |
|---|---|---|
payTo | Often documented as “who gets paid” / recipient identity | For 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 shape | Wallet or app builds something that proves payment | Proofs 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 tx | Implementation-defined | Optional: POST /build-exact-payment-tx or POST /build-sla-escrow-payment-tx return bincode VersionedTransaction shells + verifyBodyTemplate. |
| Versioned tx features | Not specified | Transactions that use address lookup tables (loaded addresses) are rejected until explicitly supported; use static-account-key shells only. |
| Who signs | Varies | Often two signers when the facilitator pays Solana fees (fee payer + payer authority); see build response notes and OpenAPI. |
| Facilitator URL | May be implied | You must call the same deployment the seller used to define rails (check seller docs or capabilities on that host). |
| Payment mint allowlist | Not in the abstract spec | If 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
Walk this in order when a seller returns 402 JSON:
- Read
accepts[]and choose one line matching your payer wallet, chain, and asset. - Confirm facilitator — Use the seller-documented base URL, or the host that issued discovery for that merchant (must match
extra.escrowProgramId/ network for escrow). GET /capabilitieson that host — Confirmfeatures(e.g.unsignedExactPaymentTxBuild,unsignedSlaEscrowPaymentTxBuild,slaEscrow).- Build (recommended) —
POSTthe matching build endpoint withpayer,accepted(the line you chose), and scheme-specific fields (resource,slaHash,oracleAuthority, etc. per OpenAPI). - Deserialize the returned
transaction(base64 → bincode →VersionedTransaction). Do not add address lookup tables. - Sign all required signer slots (see response
notes; partial sign first when facilitator is fee payer, then facilitator signs at settle if applicable). - Fill template — Paste the signed tx base64 into
verifyBodyTemplate. KeeppaymentPayload.acceptedandpaymentRequirementsbyte-for-byte identical (same JSON object). POST /verifythenPOST /settlewith the same body; reuseX-Correlation-ID/ bodycorrelationIdif the seller or your agent needs audit linkage (facilitator may mint an id on successful verify when DB is enabled).- Authorized Access (Resource Provider): Submit the finalized JSON proof to the resource provider in the
PAYMENT-SIGNATUREheader (x402 v2).- Optimization: You can send the raw JSON string directly (preferred) or Base64-encode it. All X402 v2-compliant servers now support both.
PAYMENT-RESPONSE: After settlement, x402 v2 compliant sellers return aPAYMENT-RESPONSEheader (base64-encoded JSON) containing the settlement result (success,transaction,network,payer). Buyer agents can inspect this header to confirm on-chain finality without polling.
Expiry: Solana blockhashes expire—if verify/simulate fails with blockhash errors, rebuild the unsigned tx and re-sign.
1. Discover
# 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[].scheme | Endpoint | Who signs before verify |
|---|---|---|
exact | POST /api/v1/facilitator/build-exact-payment-tx | Payer signs token authority; facilitator is fee payer at settle (default). |
sla-escrow | POST /api/v1/facilitator/build-sla-escrow-payment-tx | Buyer 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
Use your stack’s Solana signer. Replace paymentPayload.payload.transaction in verifyBodyTemplate with the signed tx base64. Keep accepted identical to paymentRequirements.
4. Verify and settle
Use the same JSON body for both calls.
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 .Scheme naming
pr402 uses short canonical names on the wire:
| Canonical (use this) | Qualified alias (also accepted) | Description |
|---|---|---|
exact | v2:solana:exact | UniversalSettle instant settlement |
sla-escrow | v2:solana:sla-escrow | SLA-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:
| Feature | Benefit |
|---|---|
verifyBodyTemplate | Build 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 /settle | If the transaction is already confirmed on-chain, settle returns success. Safe for retries, agent loops, and network interruptions. |
/upgrade (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 support | Both exact (instant UniversalSettle) and sla-escrow (time-bound escrow with oracle adjudication) are supported from a single facilitator deployment. |
/discovery (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-Headers | PAYMENT-RESPONSE, X-Correlation-ID, and X-API-Version are exposed so browser-based agents can read settlement results. |
| Toxic asset protection | Configurable mint allowlist (PR402_ALLOWED_PAYMENT_MINTS) prevents settlement with worthless spam tokens. |
| Seller quick start | Language-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(parameterstable 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.sqlas 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
{
"dryRun": false,
"limit": 50,
"cooldownSeconds": 300,
"requireRecentSettleWithinSeconds": 86400
}