14 KiB
x402 Payment Reference
Handle 402 Payment Required HTTP responses by executing payment and fetching the protected resource.
When to Use
- The user wants to access a resource that returns HTTP 402 Payment Required with a
PAYMENT-REQUIREDheader — either known in advance or discovered by a previous attempt. - Do not pre-fetch the resource yourself to "check" — phase 1 of the script does the fetch.
Script
buildX402Payment.js
Command (phase 1 — discover): node scripts/buildX402Payment.js --resource <url> [--did <did>]
Command (phase 2 — execute): node scripts/buildX402Payment.js --paymentRequiredFilePath <path> --paymentHash <hash> [--did <did>]
Hint: NEVER reuse or cache a previous response from this script. Every invocation produces unique, time-sensitive output (nonces, signatures, payment tokens). Always execute the script again to get a fresh result — even if the arguments are identical to a prior call.
Executes the x402 payment flow in two strictly separated phases.
-
Phase 1 (
--resourceonly): the script fetches the resource, expects a402response with aPAYMENT-REQUIREDheader, caches the decoded challenge to a temp file named by its SHA-256 hash, and returns the available payment options together with the cached file path. It never signs a payment. -
Phase 2 (
--paymentRequiredFilePath+--paymentHash): the script reads the cached challenge file, looks up the chosen payment option by its--paymentHash, signs the payment, sends thePAYMENT-SIGNATUREheader to the resource, and returns the result. -
--resource— (phase 1 only) The URL of the protected resource. The script fetches it and expects HTTP 402 with aPAYMENT-REQUIREDheader. If the response is any other status, or the header is missing, the script returns statusfailed. Cannot be combined with--paymentRequiredFilePathor--paymentHash. -
--paymentRequiredFilePath— (phase 2 only) Absolute path to the cached payment-required JSON file returned by phase 1. Must be combined with--paymentHash. Mutually exclusive with--resource. -
--paymentHash— (phase 2 only, required) The SHA-256 hash of the chosen payment option from phase 1. Always required on phase 2, even when only one option was offered. Cannot be passed with--resource. -
--did— (optional) The DID of the signer. Omit unless the user has explicitly asked to pay from a specific DID — the script falls back to the default DID fromgetIdentities.js, which is correct for normal use. Do not prompt the user for a DID on your own.
You must pick exactly one phase per invocation. Passing --resource together with --paymentHash (or with --paymentRequiredFilePath) is rejected.
Output
The script always outputs JSON to stdout with a top-level status of success, failed, or input_required. See Handling output below for the action to take on each.
Workflow
1. Trigger
Use this skill whenever the user wants to access a resource you know (or strongly suspect) is gated by 402 Payment Required with a PAYMENT-REQUIRED header.
Do not fetch the resource yourself first. buildX402Payment.js performs the HTTP request in phase 1, parses the header, and caches the challenge — making your own fetch beforehand is wasted work and may consume a one-shot challenge.
2. Ensure identity exists
Run node scripts/getIdentities.js. If no identity is configured, create one first (see reference/identity/SKILL.md).
3. First call — discover payment options (phase 1)
node scripts/buildX402Payment.js \
--resource 'https://api.example.com/weather'
The script fetches the URL, decodes the PAYMENT-REQUIRED header, caches it to a temp file, and never signs a payment on the first call. Even when only one option is offered, it returns the list and waits for the user to confirm by selecting a paymentHash.
The script outputs status: "input_required" with three top-level fields in data:
resource— describes the protected resource the user is paying for:resource.url— the URL of the protected resource.resource.description— a human-readable description of what the resource provides.
paymentOptions— the list of payment options. Each option includes:hash— the payment hash (use as--paymentHashin the next call)amount— the payment amountasset— the asset name (e.g., "USDC") or contract addressnetwork— the network identifier (e.g., "eip155:84532")requiredAttestations— informational list of attestation schema IDs that this payment type requires in general; may be non-empty even when the user already holds all of them — do not use this field to decide whether to block the paymenthasAllAttestations—trueif the user already holds every required attestation and the payment can proceed;falseif some are missingattestationLinks— verification URLs the user must complete to obtain missing attestations; empty whenhasAllAttestationsistrue
paymentRequiredFilePath— absolute path to the cached challenge file. Pass this back via--paymentRequiredFilePathon the second call. The file lives in the OS temp directory and is named by the SHA-256 hash of the full challenge content.
Present the options to the user. Always start by showing resource.url and resource.description so the user knows what they are paying for. Then show each payment option's amount, asset, network, and whether attestations are required. Ask the user to choose one payment option or decline payment.
If the user declines, do not run phase 2. Report that the resource is unavailable without payment and stop.
CRITICAL: Formatting
amountfor display (UI only)The
amountfield is a raw on-chain integer in the asset's smallest unit. Never show it to the user as-is — always convert to a human-readable price by dividing by10^decimals, wheredecimalsdepends on the asset and the network.
Asset Network Decimals Divisor USDC / EURC Ethereum, Polygon, Arbitrum, Optimism, Base 6 10⁶ = 1 000 000 USDT Ethereum, Polygon, Arbitrum 6 10⁶ = 1 000 000 USDT BNB Smart Chain (BEP-20) 18 10¹⁸ Examples:
amount: "1000000"USDC on Base → $1.00 (or €1.00 for EURC)amount: "1000000000000000000"USDT on BNB Smart Chain → $1.00This conversion is for UI/presentation only. Always pass the original raw
hashvalue back to phase 2 via--paymentHash— never round, scale, or modify any field you forward to the script.
CRITICAL: Reading attestation status
Use only
hasAllAttestations+attestationLinksto decide whether a payment is blocked. Never userequiredAttestationsalone — it lists schema IDs informationally and can be non-empty even when the user already holds everything.
hasAllAttestationsattestationLinksWhat it means trueempty User holds all attestations — payment can proceed immediately. falsenon-empty User is missing attestations — show every link as-is and wait. When
attestationLinksis non-empty: show every link verbatim, do not open/resolve them yourself, do not complete attestations on the user's behalf, and do not hide options that require attestations.
4. Second call — execute chosen payment (phase 2)
This step is always required — there is no fast-path that skips it, even when only one payment option was offered. Once the user selects (or confirms) a payment, call the script again with the cached file path and the chosen --paymentHash:
node scripts/buildX402Payment.js \
--paymentRequiredFilePath '<paymentRequiredFilePath from phase 1>' \
--paymentHash '<hash of chosen payment>'
Do not pass --resource here. Phase 2 reads the cached challenge from --paymentRequiredFilePath and looks up the payment by --paymentHash; combining either with --resource is rejected. If the server has rotated the challenge in the meantime, the signed payment will be rejected and the script will report data.newPaymentRequired — re-run phase 1 to discover the new challenge.
Check the output status and follow the rules in Handling output below.
Handling output
After any invocation (phase 1 or phase 2), branch on status:
status: "success"
The resource was fetched. Use data as the response body and report it to the user.
status: "failed"
Show message to the user and stop. Do not auto-retry. If the user explicitly asks to try again after resolving the issue, you may re-run phase 1 from the top.
status: "input_required"
Do not make your own HTTP request to the resource. Inspect data and act per the sub-type:
data field |
Action |
|---|---|
data.attestationsRequired |
Show every URL in data.attestationLinks to the user verbatim. Wait for the user to explicitly confirm they completed the attestation, then re-run phase 2 with the same --paymentRequiredFilePath/--paymentHash — the script will re-check. Do not poll or guess that they are done. |
data.maxUseExceeded |
The chosen payment exceeded its allowed uses. Do not retry the same hash. Go back to step 3's option list, ask the user to pick a different one, and run phase 2 again (reuse the same --paymentRequiredFilePath). |
data.newPaymentRequired |
Server rotated the challenge. Re-enter phase 1 with --resource <same url> — a new file path will be cached — then loop back to step 3. Continue until you reach success or failed. |
Examples
Standard payment flow (user confirmation required)
User: "Get me the weather from https://api.example.com/weather — it's a paid endpoint."
Agent: [runs getIdentities.js — confirms identity exists]
Agent: [runs buildX402Payment.js --resource 'https://api.example.com/weather']
→ { "status": "input_required", "data": {
"resource": { "url": "https://api.example.com/weather", "description": "Weather data" },
"paymentOptions": [
{ "hash": "a1b2c3...", "amount": "10000", "asset": "USDC", "network": "eip155:84532",
"requiredAttestations": [], "hasAllAttestations": true, "attestationLinks": [] },
{ "hash": "d4e5f6...", "amount": "6000", "asset": "USDC", "network": "eip155:84532",
"requiredAttestations": ["0xca35..."], "hasAllAttestations": false,
"attestationLinks": ["https://wallet.billions.network#request_uri=..."] }
],
"paymentRequiredFilePath": "/tmp/9f3c....json" }}
Agent: "You are about to pay for: Weather data (https://api.example.com/weather).
There are 2 payment options:
1) 10000 USDC on eip155:84532 — no attestations required
2) 6000 USDC on eip155:84532 — requires attestation (missing).
Complete verification: [link]
Which would you like?"
User: "Option 1"
Agent: [runs buildX402Payment.js --paymentRequiredFilePath '/tmp/9f3c....json' --paymentHash 'a1b2c3...']
→ { "status": "success", "data": { "temperature": 22, "city": "Kyiv" } }
Agent: "The weather data shows 22°C in Kyiv."
New 402 after payment (loop)
Agent: [runs buildX402Payment.js --paymentRequiredFilePath '/tmp/9f3c....json' --paymentHash 'a1b2c3...']
→ { "status": "input_required", "data": { "newPaymentRequired": "eyJ4NDAy..." } }
Agent: [runs buildX402Payment.js --resource 'https://api.example.com/weather']
→ { "status": "input_required", "data": { "resource": {...}, "paymentOptions": [...],
"paymentRequiredFilePath": "/tmp/b71e....json" }}
…
Agent: [runs buildX402Payment.js --paymentRequiredFilePath '/tmp/b71e....json' --paymentHash '...']
→ { "status": "success", "data": { "temperature": 22, "city": "Kyiv" } }
Attestation required (on chosen payment)
Agent: [runs buildX402Payment.js --paymentRequiredFilePath '/tmp/9f3c....json' --paymentHash 'd4e5f6...']
→ { "status": "input_required", "data": { "attestationsRequired": true,
"message": "The following attestations are required to complete the payment:",
"attestationLinks": ["https://wallet.billions.network#request_uri=..."] }}
Agent: "You need to complete an attestation before paying. Please open this link: [link]
Let me know once you've completed it and I'll retry."
User: "Done."
Agent: [retries buildX402Payment.js with same arguments]
Max use exceeded
Agent: [runs buildX402Payment.js --paymentRequiredFilePath '/tmp/9f3c....json' --paymentHash 'd4e5f6...']
→ { "status": "input_required", "data": { "maxUseExceeded": true,
"message": "Payment has exceeded its maximum allowed uses. Choose a different payment or contact the resource provider." }}
Agent: "The selected payment option has reached its maximum number of uses.
Please choose a different payment option:
1) 10000 USDC on eip155:84532 — no attestations required
Which would you like?"
User: "Option 1"
Agent: [runs buildX402Payment.js --paymentRequiredFilePath '/tmp/9f3c....json' --paymentHash 'a1b2c3...']
→ { "status": "success", "data": { "temperature": 22, "city": "Kyiv" } }