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
/settle
returns one of two shapes. Terminal success means the transfer confirmed in the
HTTP wait window. Facilitator Service responds with:
paymentId you poll on
/status:
Step 5: Confirm the payment settled
Sign a fresh seller proof for the/status
call with purpose: "status", then poll at the interval indicated by
retryAfterMs until the payment reaches completed or failed.
Substitute the base64url proof from sign-proof.ts:
completed once the USDC transfer settled to
payTo: