Prerequisites
Before you begin, ensure that you’ve:- Installed Node.js v22.6+
- Installed viem
- Created an EVM wallet, which will be your
payToaddress 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
/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 controlpayTo 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
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:
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.
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:
completed once the USDC transfer settled to
payTo: