Skip to main content
Accept a signed x402 payment authorization from a buyer, submit it to Facilitator Service on Arc testnet, and confirm that USDC settled to your seller address. This quickstart uses the keyless trial, so no Circle account or API key is required.

Prerequisites

Before you begin, ensure that you’ve:
  • Installed Node.js v22.6+
  • Installed viem
  • Created an EVM wallet, which will be your payTo address for receiving USDC payments
  • Obtained the private key controlling payTo, which will be used to sign the seller proof
  • Funded a buyer wallet with Arc testnet USDC from the Circle Faucet

Step 1: Get a signed payment authorization from the buyer

In production, the buyer’s wallet or agent signs the EIP-3009 authorization and sends it to your API in the x402 request. For this quickstart, sign one from a test wallet you control so you can act as both buyer and seller. The authorization signs against the USDC contract on Arc testnet.
sign-authorization.ts
Keep the signature and message fields. You pass them to /settle in Step 3 as payload.signature and payload.authorization.

Step 2: Build the seller proof

The seller proof is a base64url-encoded envelope carrying an EIP-712 signature. It proves you control payTo and binds the request to the purpose and body being sent.
Sign each Facilitator Service call with a proof whose purpose matches the route (verify, settle, or status). For a step-by-step walkthrough and the full signing rules, see Sign a seller proof.
sign-proof.ts

Step 3: Submit the payment

/settle is the authoritative money path. It validates the authorization, screens buyer and seller, records durable payment state, submits the USDC transfer, and returns terminal evidence or a pending response. You can include a payment-identifier extension in the request body for idempotency scoped to your seller account. When supplied, its id must be 16 to 128 characters from [A-Za-z0-9_-]. When omitted, the facilitator uses an internal surrogate. In that case, a retry must reuse the exact same signed authorization, because a fresh authorization without an identifier is treated as a new charge.
body.json
Generate a fresh proof for this call using the sign-proof.ts script from Step 2, then pass it in the Facilitator-Seller-Proof header:

Step 4: Read the settlement response

A settlement outcome from /settle always uses HTTP 200 and takes one of two shapes. A rejected request or an internal failure uses the documented 4xx or 5xx response instead and carries no settlement outcome. Terminal success means the transfer confirmed in the HTTP wait window. Facilitator Service responds with:
Pending means confirmation did not arrive in time. Do not fulfill on pending. The response is still HTTP 200, with success: false and errorReason: settlement_pending. Use the paymentId to reconcile the outcome through /status:
transaction is empty on a pending response: Facilitator Service records a transaction hash only once terminal evidence arrives.
A timeout is not evidence of failure. To retry a pending payment, reuse the same payment-identifier you supplied on the first call. If you did not supply one, retry with the exact same signed buyer authorization. Never re-sign, or you risk charging the buyer twice.

Step 5: Confirm the payment settled

Sign a fresh seller proof for the /status call with purpose: "status", then read the payment until it reaches completed or failed. Settlement returns no retry interval, so choose your own reconcile cadence. Substitute the base64url proof from sign-proof.ts:
Facilitator Service responds with completed once the USDC transfer settled to payTo:
You’ve settled a payment through Facilitator Service. In a real x402 integration, this is when your API responds to the buyer with the paid resource.