Sign a payment
POST https://api.powaagent.ai/wallets/signatures
This endpoint is advanced reference for direct integrations. The PowaAgent connector and the Skill call it for you.
You send the payment terms from a seller's 402 Payment Required response. PowaAgent checks them against your source policy, signs an EIP-3009 payment from your wallet, and returns the value to send to the seller in the payment header. PowaAgent doesn't call the seller for you.
Request
POST /wallets/signatures HTTP/1.1
Host: api.powaagent.ai
Authorization: Bearer <your PowaAgent API key>
Content-Type: application/json
X-Sandbox-Mode: true # sandbox only; omit for live payments
Body
| Field | Type | Required | Description |
|---|---|---|---|
sellerUrl | string | Yes | The full URL you're paying for. Your source policy is checked against its host and path, and the query string is ignored. |
paymentRequired | object | Yes | The seller's payment terms, decoded from the base64 PAYMENT-REQUIRED header (or the x402 v1 response body). |
paymentRequired.x402Version | integer | Yes | 2, or 1 for x402 v1 sellers. |
paymentRequired.resource | object | Yes | url, description and mimeType, as sent by the seller. |
paymentRequired.accepts | array | Yes | The payment options. Only the first entry is signed, so put the option you want to pay first. |
Each accepts entry needs:
| Field | Description |
|---|---|
scheme | Use exact |
network | eip155:8453 (Base mainnet) or eip155:84532 (Base Sepolia, sandbox). base and base-sepolia are also accepted. |
amount | The price in USDC base units (1 USDC = 1,000,000). v1 sellers' maxAmountRequired is also accepted. |
asset | The USDC contract address |
payTo | The address to pay |
maxTimeoutSeconds | The seller's maximum signature lifetime |
extra | Required. name and version for the token, for example {"name": "USD Coin", "version": "2"} |
Example
{
"sellerUrl": "https://api.example.com/v1/weather?lat=-34.05&lon=23.37",
"paymentRequired": {
"x402Version": 2,
"resource": {
"url": "https://api.example.com/v1/weather?lat=-34.05&lon=23.37",
"description": "Current weather conditions",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "2000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x1111111111111111111111111111111111111111",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2" }
}
]
}
}
Response
200 OK
{
"paymentSignatureHeader": "eyJ4NDAyVmVyc2lvbiI6Mi...",
"validAfter": 0,
"validBefore": 1791205420,
"nonce": "0x6f1c...9a2e"
}
| Field | Description |
|---|---|
paymentSignatureHeader | The base64-encoded signed payment. Send it to the seller unchanged, in the PAYMENT-SIGNATURE header (x402 v2) or the X-PAYMENT header (x402 v1). |
validAfter | Always 0 |
validBefore | Unix time after which the signature can't be used: the earlier of 120 seconds from now and the seller's maxTimeoutSeconds |
nonce | The unique 32-byte nonce in the signed authorization |
Completing the payment
- Call the seller and get its
402response. - Decode the payment terms, choose a USDC-on-Base option and put it first in
accepts. POST /wallets/signatureswith the seller URL and the terms.- Call the seller again with
PAYMENT-SIGNATURE: <paymentSignatureHeader>, beforevalidBefore. - Read the seller's
PAYMENT-RESPONSEheader for the settlement result and transaction hash.
For sandbox calls to Premium and Standard listings, also send X-Sandbox-Mode: true to the seller, so it routes to the sandbox endpoint.
Retries
There's no idempotency key. Every request produces a new signature with a new nonce.
- If the signing request failed, send it again.
- If you already sent a signature to the seller and didn't get a response, retry the seller with the same signature rather than signing again. The USDC contract accepts each nonce once, so the same signature can't be settled twice.
Sandbox
Send X-Sandbox-Mode: true when the payment is on Base Sepolia. Sandbox payments:
- are not checked against your spend caps, and don't count towards them
- are still checked against your deny list, allow list and ecosystem switches
If you sign a Base Sepolia payment without the header, it's treated and counted as a live payment.
For test USDC, call POST https://api.powaagent.ai/wallets/testnet/funds with no body. It returns {"transactionHash", "network", "token"}. You can call it once every 24 hours, and a call inside that window returns 429. See Sandbox testing.
Errors
Errors use application/problem+json. For example:
{
"type": "https://problems.certaindata.com/allow-pending",
"title": "Seller approval pending",
"status": 403,
"detail": "Seller pending user approval: api.example.com/v1/weather"
}
See Errors for the full list.