HTTP API — One-time Payment#
This page is the API reference for one-time payment, covering the full interface definitions for the exact, upto, and charge schemes. For usage guides and SDK integration examples, see One-time payment · Integration docs.
Choose your scheme#
exact — single recipient, supports both sync and async settlement. Two asset-transfer methods are available: native EIP-3009 (only tokens that natively implement it, e.g. USDC) and Permit2 (compatible with any ERC-20).
upto — authorize a ceiling, settle by actual usage. Built for AI Agent per-call billing, streaming-service metering, free-trial-to-paid conversions, etc. Always backed by Permit2, and accepts two kinds of buyers: external EOA wallets (secp256k1) and OKX agentic wallets (Ed25519 session keys).
charge — supports a single recipient too, plus splits, i.e. one payment to multiple addresses (≤10). Defaults to and only supports sync settlement.
| Dimension | exact (EIP-3009) | exact (Permit2) | upto | charge |
|---|---|---|---|---|
| Recipient | Single | Single | Single | Single / multi (≤10) |
| Settlement timing | Sync / async | Sync / async | Async (default pending) | Sync only |
| Asset transfer | EIP-3009 transferWithAuthorization | Permit2 canonical → x402ExactPermit2Proxy | Permit2 canonical → x402UptoPermit2Proxy | EIP-3009 |
| Token compatibility | EIP-3009 tokens only | Any ERC-20 | Any ERC-20 | EIP-3009 tokens only |
| Amount semantics | signed = paid | signed = paid | signed = ceiling, paid ≤ ceiling | signed = paid |
| Integration guide | exact path | exact path | upto path | charge path |
- Base URL:
https://web3.okx.com exact/uptopath prefix:/api/v6/pay/x402(the four endpoints — verify / settle / supported / settle/status — are shared and routed automatically by payload content)chargepath prefix:/api/v6/pay/mpp/charge- Network: X Layer (chainId
196, CAIP-2 identifiereip155:196)
Authentication#
All endpoints require API Key authentication. The following headers must be provided:
| Header | Required | Description |
|---|---|---|
OK-ACCESS-KEY | Yes | API Key |
OK-ACCESS-SIGN | Yes | Request signature |
OK-ACCESS-PASSPHRASE | Yes | API passphrase |
OK-ACCESS-TIMESTAMP | Yes | ISO 8601 timestamp |
Content-Type | Yes | application/json for POST requests |
All responses use a unified envelope:
{
"code": "0",
"msg": "success",
"data": { /* business fields */ }
}
On business errors, code is non-"0" and data is null. See the Error codes section at the end of this page.
exact Scheme#
exact supports two asset-transfer methods, selected by accepted.extra.assetTransferMethod together with the payload field that is populated:
- EIP-3009 path: populate
payload.authorization;accepted.extraeither omitsassetTransferMethodor sets it toeip3009. - Permit2 path: populate
payload.permit2Authorization;accepted.extra.assetTransferMethod="permit2", andpaymentRequirements.extrais set the same way.
The two paths are mutually exclusive — payload.authorization and payload.permit2Authorization must be strictly one-or-the-other.
1. /api/v6/pay/x402/supported#
/api/v6/pay/x402/supported
Query the schemes, networks, and signers supported by the Broker. The Seller SDK calls this endpoint to construct the accepts array of the 402 response. The kinds array is emitted dynamically based on Apollo gating flags.
Request parameters#
None.
Response parameters#
| Parameter | Type | Description |
|---|---|---|
kinds | Array<SupportedKind> | Supported payment-type list |
kinds[].x402Version | Integer | Protocol version, e.g. 2 |
kinds[].scheme | String | Settlement scheme: exact / aggr_deferred / upto |
kinds[].network | String | CAIP-2 network identifier, e.g. eip155:196 |
kinds[].extra | Object | Scheme-specific extension config (see table below) |
extensions | Array<String> | Supported extension identifiers |
signers | Object | CAIP-2 wildcard → array of signer addresses |
kinds[].extra fields:
| Field | Applies to | Description |
|---|---|---|
assetTransferMethod | exact / upto | "permit2" means settlement goes through Permit2 canonical + Proxy contract. When omitted on exact, native EIP-3009 is used |
facilitatorAddress | upto | Facilitator EOA address. The buyer must put the same address in witness.facilitator |
signatureSchemes | upto | Signature algorithms currently supported by the facilitator. ["ed25519"] when EOA path is gated off; ["ed25519", "secp256k1"] after the EOA path is enabled |
Request example#
curl --location --request GET 'https://web3.okx.com/api/v6/pay/x402/supported' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z'
Response example#
{
"code": "0",
"msg": "",
"data": {
"kinds": [
{ "x402Version": 2, "scheme": "exact", "network": "eip155:196" },
{ "x402Version": 2, "scheme": "exact", "network": "eip155:196",
"extra": { "assetTransferMethod": "permit2" } },
{ "x402Version": 2, "scheme": "aggr_deferred", "network": "eip155:196" },
{ "x402Version": 2, "scheme": "upto", "network": "eip155:196",
"extra": {
"assetTransferMethod": "permit2",
"facilitatorAddress": "0xFacilitatorEOA...",
"signatureSchemes": ["ed25519", "secp256k1"]
} }
],
"extensions": [],
"signers": {}
}
}
2. /api/v6/pay/x402/verify#
/api/v6/pay/x402/verify
Validate the Buyer's signed payment authorization. No on-chain transaction is executed.
Request parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
x402Version | Integer | Yes | Protocol version, e.g. 2 |
paymentPayload | Object | Yes | The payment payload the client sends with the protected request. See PaymentPayload |
paymentRequirements | Object | Yes | The Seller-defined payment requirements. See PaymentRequirements |
Constraints:
paymentPayload.accepted.schememust matchpaymentRequirements.scheme; allowed values are"exact"and"upto".paymentPayload.payload.authorizationandpaymentPayload.payload.permit2Authorizationmust be strictly one-or-the-other:exact + EIP-3009: populateauthorizationexact + Permit2: populatepermit2Authorization, withaccepted.scheme="exact"upto: populatepermit2Authorization, withaccepted.scheme="upto"andwitness.facilitatorrequired
Response parameters#
| Parameter | Type | Description |
|---|---|---|
isValid | Boolean | true = passed, false = failed |
invalidReason | String | Machine-readable invalid reason (returned on failure) |
invalidMessage | String | Human-readable invalid message (returned on failure) |
payer | String | Payer wallet address |
Request example — exact + EIP-3009#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/x402/verify' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"x402Version": 2,
"paymentPayload": {
"x402Version": 2,
"resource": {
"url": "https://api.example.com/premium-data",
"description": "Access to premium data",
"mimeType": "application/json"
},
"accepted": {
"scheme": "exact",
"network": "eip155:196",
"amount": "10000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xRecipientAddress",
"maxTimeoutSeconds": 60,
"extra": { "name": "USDG", "version": "2" }
},
"payload": {
"signature": "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480...",
"authorization": {
"from": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"to": "0xRecipientAddress",
"value": "10000",
"validAfter": "0",
"validBefore": "1740672154",
"nonce": "0xf374661..."
}
}
},
"paymentRequirements": {
"scheme": "exact",
"network": "eip155:196",
"amount": "10000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xRecipientAddress",
"maxTimeoutSeconds": 60,
"extra": { "name": "USDG", "version": "2" }
}
}'
Request example — exact + Permit2#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/x402/verify' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"x402Version": 2,
"paymentPayload": {
"x402Version": 2,
"resource": { "url": "https://api.example.com/premium-data" },
"accepted": {
"scheme": "exact",
"network": "eip155:196",
"amount": "10000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xMerchantAddr...",
"maxTimeoutSeconds": 60,
"extra": { "assetTransferMethod": "permit2" }
},
"payload": {
"signature": "0xf374...1c",
"permit2Authorization": {
"from": "0xBuyerEOA...",
"permitted": {
"token": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"amount": "10000"
},
"spender": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001",
"nonce": "1027389471020934876123987612938712398761239",
"deadline": "1714813500",
"witness": {
"to": "0xMerchantAddr...",
"validAfter": "1714812840"
}
}
}
},
"paymentRequirements": {
"scheme": "exact",
"network": "eip155:196",
"amount": "10000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xMerchantAddr...",
"extra": { "assetTransferMethod": "permit2" }
}
}'
Notes:
permit2Authorization.witnessonly containsto/validAfterhere — nofacilitator(the DTO has the field, but the exact path ignores it).spendermust equal Apollox402.exact.permit2.proxy.address, otherwiseinvalid_permit2_spenderis returned.permit2Authorization.permitted.amountmust equalpaymentRequirements.amount(signed = paid).
Response example — verification passed#
{
"code": "0",
"msg": "success",
"data": {
"isValid": true,
"invalidReason": null,
"invalidMessage": null,
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d"
}
}
Response example — verification failed#
{
"code": "0",
"msg": "success",
"data": {
"isValid": false,
"invalidReason": "insufficient_allowance",
"invalidMessage": "Insufficient allowance to Permit2: allowance=0, required=10000",
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d"
}
}
3. /api/v6/pay/x402/settle#
/api/v6/pay/x402/settle
After verification passes, submit the on-chain settlement. Each call initiates an independent on-chain transaction:
exact + EIP-3009: calls the token contract'stransferWithAuthorizationdirectly.exact + Permit2: callsx402ExactPermit2Proxy.settle. The buyer must have alreadyapprove(MAX_UINT256)d the ERC-20 to the Permit2 canonical contract.upto: callsx402UptoPermit2Proxy.settle. The Facilitator co-signs (HSM) before broadcasting.
Request parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
x402Version | Integer | Yes | Protocol version, e.g. 2 |
paymentPayload | Object | Yes | Same as verify |
paymentRequirements | Object | Yes | Same as verify. For upto, amount is the actual paid amount and must be ≤ permit2Authorization.permitted.amount (the ceiling) |
syncSettle | Boolean | No | OKX extension. true = wait for on-chain confirmation (poll until timeout); false (default) = async broadcast. upto ignores this field and is always async |
Response parameters#
| Parameter | Type | Description |
|---|---|---|
success | Boolean | Whether settlement succeeded |
errorReason | String | Machine-readable failure reason (returned on failure) |
errorMessage | String | Human-readable failure message (returned on failure) |
payer | String | Payer wallet address |
transaction | String | On-chain transaction hash |
network | String | CAIP-2 network identifier |
status | String | OKX extension. Settlement status — see table below |
amount | String | OKX extension. upto returns the actual settled amount (atomic units); zero-settle returns "0". Not returned for exact / exact + permit2 |
status matrix:
| Path | syncSettle | Result | status | transaction | amount |
|---|---|---|---|---|---|
exact (EIP-3009 / Permit2) | false (default) | Broadcast | pending | txHash | — |
exact (EIP-3009 / Permit2) | true | Confirmed on-chain | success | txHash | — |
exact (EIP-3009 / Permit2) | true | Wait timeout | timeout | txHash | — |
exact (EIP-3009 / Permit2) | — | Verify / simulation / on-chain failure | failed | "" | — |
upto | — (ignored) | Zero-settle | success | null | "0" |
upto | — (ignored) | Broadcast | pending | txHash | Actual amount |
upto | — (ignored) | Verify / simulation / on-chain failure | failed | "" | — |
Request example — exact + EIP-3009 sync settle#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/x402/settle' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"x402Version": 2,
"paymentPayload": { "...same as verify..." },
"paymentRequirements": { "...same as verify..." },
"syncSettle": true
}'
Request example — exact + Permit2 async settle#
The body is identical to verify — populate paymentPayload.payload.permit2Authorization and omit syncSettle.
Response example — exact sync settle success (syncSettle=true)#
{
"code": "0",
"msg": "success",
"data": {
"success": true,
"errorReason": null,
"errorMessage": null,
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
"transaction": "0x4f46ed8eac92ddbccfb56a88ff827db3616c7beb191adabbeeded901340bd7d5",
"network": "eip155:196",
"status": "success"
}
}
Response example — exact async settle (syncSettle=false)#
{
"code": "0",
"msg": "success",
"data": {
"success": true,
"errorReason": null,
"errorMessage": null,
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
"transaction": "0x4f46ed8eac92ddbccfb56a88ff827db3616c7beb191adabbeeded901340bd7d5",
"network": "eip155:196",
"status": "pending"
}
}
Response example — exact wait timeout (syncSettle=true)#
{
"code": "0",
"msg": "success",
"data": {
"success": true,
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
"transaction": "0x4f46ed8eac92ddbccfb56a88ff827db3616c7beb191adabbeeded901340bd7d5",
"network": "eip155:196",
"status": "timeout"
}
}
When the client sees
timeout, fall back to polling/settle/status.
Response example — settle failure#
{
"code": "0",
"msg": "success",
"data": {
"success": false,
"errorReason": "insufficient_funds",
"errorMessage": "Transaction reverted",
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
"transaction": "",
"network": "eip155:196",
"status": "failed"
}
}
4. /api/v6/pay/x402/settle/status#
/api/v6/pay/x402/settle/status
Query settlement status by on-chain transaction hash. Used for polling in async settlement (syncSettle=false, upto, or a syncSettle=true that timed out).
Request parameters#
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
txHash | query | String | Yes | On-chain transaction hash |
Response parameters#
| Parameter | Type | Description |
|---|---|---|
success | Boolean | Whether the query succeeded (false if txHash not found; also false if settleStatus = FAILED) |
errorReason | String | Machine-readable failure reason (DB error_reason field) |
errorMessage | String | Human-readable failure message |
payer | String | Payer wallet address |
transaction | String | On-chain transaction hash |
network | String | CAIP-2 network identifier |
status | String | Current settlement status: pending / success / failed |
amount | String | OKX extension. upto returns the actual settled amount (atomic units); null for exact / exact + permit2 |
Request example#
curl --location --request GET 'https://web3.okx.com/api/v6/pay/x402/settle/status?txHash=0x4f46ed8eac92ddbccfb56a88ff827db3616c7beb191adabbeeded901340bd7d5' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z'
Response example — query success (exact)#
{
"code": "0",
"msg": "success",
"data": {
"success": true,
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
"transaction": "0x4f46ed8eac92ddbccfb56a88ff827db3616c7beb191adabbeeded901340bd7d5",
"network": "eip155:196",
"status": "success",
"amount": null
}
}
Response example — query success (upto)#
{
"code": "0",
"msg": "success",
"data": {
"success": true,
"payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
"transaction": "0xabc...",
"network": "eip155:196",
"status": "success",
"amount": "1234000"
}
}
Response example — transaction not found#
{
"code": "0",
"msg": "success",
"data": {
"success": false,
"errorReason": "not_found",
"errorMessage": "Transaction not found for txHash: 0xabc123...",
"payer": null,
"transaction": null,
"network": null,
"status": null
}
}
upto Scheme#
upto = "authorize a ceiling, settle by actual usage." Always backed by Permit2 canonical → x402UptoPermit2Proxy, with witness.facilitator required. The Facilitator co-signs (owner, token, permittedAmount, permitNonce, permitDeadline, witnessHash, userSignatureHash, amount, relayer, deadline) via HSM before the proxy is invoked.
The four endpoints under /api/v6/pay/x402/{verify,settle,supported,settle/status} are shared with exact. Routing happens automatically when paymentPayload.accepted.scheme="upto" and payload.permit2Authorization is present.
Dual signature paths#
upto accepts two kinds of buyers, dispatched by accepted.extra.sessionCert together with accepted.extra.signatureScheme:
| Path | Wallet | Signature algorithm | sessionCert | signatureScheme |
|---|---|---|---|---|
| EOA | MetaMask / Rabby / Coinbase Wallet, etc. | secp256k1 (EIP-712) | omitted | omitted / "secp256k1" |
| SESSION | OKX agentic wallet | Ed25519 (session key) | required (base64 sessionCert) | omitted / "ed25519" |
Mutual-exclusivity violations (sessionCert set together with signatureScheme="secp256k1", or signatureScheme="ed25519" without sessionCert) → upto_signature_route_conflict.
The EOA path is gated by Apollo x402.upto.eoa.signature.enabled. While gated off, the EOA path returns unsupported_scheme.
5. /api/v6/pay/x402/verify — scheme=upto#
/api/v6/pay/x402/verify
The body shape is identical to exact + Permit2. Differences:
| Field | upto requirement |
|---|---|
paymentPayload.accepted.scheme | Must be "upto" |
paymentRequirements.scheme | Must be "upto" |
paymentPayload.payload.permit2Authorization.witness.facilitator | Required and must be in the facilitator allowlist (Apollo x402.upto.facilitator.address) |
paymentPayload.accepted.extra.facilitatorAddress | Must equal witness.facilitator (case-insensitive) |
paymentPayload.accepted.extra.sessionCert | Required on SESSION path, omitted on EOA path |
paymentPayload.accepted.extra.signatureScheme | Optional: "secp256k1" / "ed25519". Auto-detected from sessionCert when omitted |
paymentPayload.payload.permit2Authorization.spender | Must equal Apollo x402.upto.permit2.proxy.address (different from the exact + permit2 proxy); otherwise invalid_spender |
paymentPayload.payload.permit2Authorization.permitted.amount | Authorization ceiling, must be > 0. At verify time, permitted.amount == paymentRequirements.amount is enforced — the buyer commits both the ceiling and the requirement amount up-front, and the seller overrides paymentRequirements.amount with the actual paid amount at settle time |
Request example — upto + EOA#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/x402/verify' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"x402Version": 2,
"paymentPayload": {
"x402Version": 2,
"resource": { "url": "https://api.example.com/agent-call" },
"accepted": {
"scheme": "upto",
"network": "eip155:196",
"amount": "5000000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xMerchant...",
"maxTimeoutSeconds": 60,
"extra": {
"assetTransferMethod": "permit2",
"facilitatorAddress": "0xFacilitatorEOA..."
}
},
"payload": {
"signature": "0x...65byte_secp256k1_sig",
"permit2Authorization": {
"from": "0xBuyerEOA...",
"permitted": {
"token": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"amount": "5000000"
},
"spender": "0x4020e7...0002",
"nonce": "1027389471020934876",
"deadline": "1714813500",
"witness": {
"to": "0xMerchant...",
"facilitator": "0xFacilitatorEOA...",
"validAfter": "1714812840"
}
}
}
},
"paymentRequirements": {
"scheme": "upto",
"network": "eip155:196",
"amount": "5000000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xMerchant...",
"extra": {
"assetTransferMethod": "permit2",
"facilitatorAddress": "0xFacilitatorEOA..."
}
}
}'
Request example — upto + SESSION (OKX agentic wallet)#
accepted.extra carries two extra fields, and payload.signature is a base64-encoded Ed25519 signature:
{
"accepted": {
"scheme": "upto",
"network": "eip155:196",
"amount": "5000000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xMerchant...",
"extra": {
"assetTransferMethod": "permit2",
"facilitatorAddress": "0xFacilitatorEOA...",
"sessionCert": "eyJhbGciOi...base64",
"signatureScheme": "ed25519"
}
},
"payload": {
"signature": "MEUCIQ...base64_ed25519_sig",
"permit2Authorization": {
"from": "0xAaAddress...",
"permitted": { "token": "0x4ae46a...", "amount": "5000000" },
"spender": "0x4020e7...0002",
"nonce": "1027389471020934876",
"deadline": "1714813500",
"witness": {
"to": "0xMerchant...",
"facilitator": "0xFacilitatorEOA...",
"validAfter": "1714812840"
}
}
}
}
Response#
Same shape as exact verify: {isValid, invalidReason, invalidMessage, payer}.
6. /api/v6/pay/x402/settle — scheme=upto#
/api/v6/pay/x402/settle
The body shape is identical to verify. Differences:
paymentRequirements.amountis the actual paid amount, with0 ≤ amount ≤ permit2Authorization.permitted.amount.paymentRequirements.amount = "0"enters the zero-settle fast path: no TEE call, no on-chain transaction, the DB row is marked SUCCESS, and the response returnstransaction=null/status=success/amount="0".syncSettleis ignored;uptoalways returnspendingasynchronously.
Settle response matrix#
| Scenario | success | status | transaction | amount | errorReason |
|---|---|---|---|---|---|
| Zero-settle | true | success | null | "0" | — |
| Normal broadcast (async) | true | pending | txHash | actual amount | — |
| settle amount > ceiling | false | failed | "" | — | upto_settlement_exceeds_amount |
| Facilitator field invalid | false | failed | "" | — | upto_facilitator_mismatch |
| EOA signature format invalid | false | failed | "" | — | invalid_eoa_signature |
| TEE co-sign failed (SESSION) | false | failed | "" | — | tee_sign_failed |
| Intent submission failed | false | failed | "" | — | intent_submit_failed |
| Same (payer, nonce) replayed | true | previous status | previous txHash or "" | DB settle_amount or "0" | — |
Request example#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/x402/settle' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"x402Version": 2,
"paymentPayload": {
"x402Version": 2,
"accepted": {
"scheme": "upto",
"network": "eip155:196",
"amount": "5000000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xMerchant...",
"extra": {
"assetTransferMethod": "permit2",
"facilitatorAddress": "0xFacilitatorEOA..."
}
},
"payload": {
"signature": "0x...secp256k1_sig",
"permit2Authorization": { "...same as verify..." }
}
},
"paymentRequirements": {
"scheme": "upto",
"network": "eip155:196",
"amount": "1234000",
"asset": "0x4ae46a509f6b1d9056937ba4500cb143933d2dc8",
"payTo": "0xMerchant...",
"extra": {
"assetTransferMethod": "permit2",
"facilitatorAddress": "0xFacilitatorEOA..."
}
}
}'
Response example — normal broadcast#
{
"code": "0",
"msg": "success",
"data": {
"success": true,
"payer": "0xbuyer...",
"transaction": "0xabc...",
"network": "eip155:196",
"status": "pending",
"amount": "1234000"
}
}
Response example — zero-settle#
{
"code": "0",
"msg": "success",
"data": {
"success": true,
"payer": "0xbuyer...",
"transaction": null,
"network": "eip155:196",
"status": "success",
"amount": "0"
}
}
Response example — failure#
{
"code": "0",
"msg": "success",
"data": {
"success": false,
"errorReason": "upto_settlement_exceeds_amount",
"errorMessage": "Settlement amount exceeds authorized ceiling",
"payer": "0xbuyer...",
"transaction": "",
"network": "eip155:196",
"status": "failed"
}
}
charge Scheme#
charge provides one-time token transfer based on an HTTP 402 Challenge-Credential flow.
- Server-side payment (transaction mode): the Buyer signs an EIP-3009 authorization, and the Broker submits the on-chain transaction on their behalf
- Client-side payment (hash mode): the Buyer broadcasts the on-chain transaction themselves, and the Broker verifies its validity
7. /api/v6/pay/mpp/charge/settle#
/api/v6/pay/mpp/charge/settle
Server-side payment — submit the on-chain ERC-20 transfer on behalf of the user. Supports the EIP-3009 authorization scheme and splits (up to 10).
Request parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
challenge | Object | Yes | The Challenge object issued by the server (echo back as-is). See Challenge |
payload | Object | Yes | EVM payment receipt |
payload.type | String | Yes | Always "transaction" |
payload.authorization | Object | Yes | EIP-3009 authorization object |
payload.authorization.type | String | Yes | Always "eip-3009" |
payload.authorization.from | String | Yes | Payer wallet address |
payload.authorization.to | String | Yes | Recipient wallet address |
payload.authorization.value | String | Yes | Payment amount (base units) |
payload.authorization.validAfter | String | Yes | Authorization start Unix timestamp |
payload.authorization.validBefore | String | Yes | Authorization expiry Unix timestamp |
payload.authorization.nonce | String | Yes | Random bytes32, unique per authorization |
payload.authorization.signature | String | Yes | 65-byte EIP-712 signature (r‖s‖v) |
payload.authorization.splits | Array | No | Splits list (max 10). See Split |
source | String | No | Payer DID (did:pkh:eip155:196:0x...) |
Response parameters#
| Parameter | Type | Description |
|---|---|---|
method | String | Always "evm" |
reference | String | On-chain transaction hash (0x-prefixed) |
status | String | Always "success" |
timestamp | String | RFC 3339 settlement time |
chainId | Integer | Settlement chain ID, e.g. 196 |
challengeId | String | Challenge ID, for client correlation |
externalId | String | Echoed merchant order ID from the Challenge request |
Request example#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/charge/settle' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"challenge": {
"id": "qB3wErTyU7iOpAsD9fGhJk",
"realm": "api.example.com",
"method": "evm",
"intent": "charge",
"request": "eyJhbW91bnQiOiIxMDAwMCIsImN1cnJlbmN5Ijoi...",
"expires": "2026-04-01T12:05:00Z"
},
"payload": {
"type": "transaction",
"authorization": {
"type": "eip-3009",
"from": "0x1234567890abcdef1234567890abcdef12345678",
"to": "0x742d35Cc6634c0532925a3b844bC9e7595F8fE00",
"value": "10000",
"validAfter": "0",
"validBefore": "9999999999",
"nonce": "0x9337d07c707c703b86f05e66b9097e38e7587e7ecfe740551ac608693864abdd",
"signature": "0x5a9827232b5c640d7239462dbb3f0eede1aa2522eb53e552369db8db66720293..."
}
}
}'
Response example#
{
"code": "0",
"msg": "",
"data": {
"method": "evm",
"reference": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"status": "success",
"timestamp": "2026-04-01T12:04:30Z",
"chainId": 196,
"challengeId": "qB3wErTyU7iOpAsD9fGhJk",
"externalId": "order-12345"
}
}
Request example — with splits#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/charge/settle' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"challenge": {
"id": "sP1itPaym3ntEx4mple",
"realm": "marketplace.example.com",
"method": "evm",
"intent": "charge",
"request": "eyJ...",
"expires": "2026-04-01T12:05:00Z"
},
"payload": {
"type": "transaction",
"authorization": {
"type": "eip-3009",
"from": "0x1234567890abcdef1234567890abcdef12345678",
"to": "0x742d35Cc6634c0532925a3b844bC9e7595F8fE00",
"value": "940000",
"validAfter": "0",
"validBefore": "1775059500",
"nonce": "0x1111111111111111111111111111111111111111111111111111111111111111",
"signature": "0xabc...primary",
"splits": [
{
"from": "0x1234567890abcdef1234567890abcdef12345678",
"to": "0xA1B2C3d4e5F6a1B2c3d4e5F6a1b2c3d4e5F6a1b2",
"value": "50000",
"validAfter": "0",
"validBefore": "1775059500",
"nonce": "0x2222222222222222222222222222222222222222222222222222222222222222",
"signature": "0xdef...split1"
},
{
"from": "0x1234567890abcdef1234567890abcdef12345678",
"to": "0xC4D5e6F7A8B9c4D5E6f7a8B9c4d5e6F7a8b9C4D5",
"value": "10000",
"validAfter": "0",
"validBefore": "1775059500",
"nonce": "0x3333333333333333333333333333333333333333333333333333333333333333",
"signature": "0xghi...split2"
}
]
}
},
"source": "did:pkh:eip155:196:0x1234567890abcdef1234567890abcdef12345678"
}'
8. /api/v6/pay/mpp/charge/verifyHash#
/api/v6/pay/mpp/charge/verifyHash
Client-side payment — verify that the on-chain transaction broadcast by the client matches the payment requirements in the Challenge.
Request parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
challenge | Object | Yes | The Challenge object issued by the server (echo back as-is). See Challenge |
payload | Object | Yes | Payment receipt |
payload.type | String | Yes | Always "hash" |
payload.hash | String | Yes | The on-chain transaction hash already broadcast by the client |
source | String | Yes | Payer DID (did:pkh:eip155:196:0x...) |
Response parameters#
| Parameter | Type | Description |
|---|---|---|
method | String | Always "evm" |
reference | String | On-chain transaction hash (0x-prefixed) |
status | String | Always "success" |
timestamp | String | RFC 3339 confirmation time |
chainId | Integer | Chain ID, e.g. 196 |
challengeId | String | Challenge ID, for client correlation |
externalId | String | Echoed merchant order ID from the Challenge request |
Request example#
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/charge/verifyHash' \
--header 'Content-Type: application/json' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2026-04-01T12:21:41.274Z' \
--data '{
"challenge": {
"id": "qB3wErTyU7iOpAsD9fGhJk",
"realm": "api.example.com",
"method": "evm",
"intent": "charge",
"request": "eyJhbW91bnQiOiIxMDAwMCIsImN1cnJlbmN5Ijoi...",
"expires": "2026-04-01T12:05:00Z"
},
"payload": {
"type": "hash",
"hash": "0xd9a703784f0cb489ea90c52f5626a22516f39c5063558733bb742972fdf6f722"
},
"source": "did:pkh:eip155:196:0x1234567890abcdef1234567890abcdef12345678"
}'
Response example#
{
"code": "0",
"msg": "",
"data": {
"method": "evm",
"reference": "0x9f8e7d6c5b4a3928170fabcdef1234567890abcdef1234567890abcdef123456",
"status": "success",
"timestamp": "2026-04-01T12:04:30Z",
"chainId": 196,
"challengeId": "qB3wErTyU7iOpAsD9fGhJk",
"externalId": "order-12345"
}
}
Common data structures#
PaymentPayload#
After signing, the Buyer passes this through the X-PAYMENT header (base64-encoded) to the Seller, who forwards it as-is to the Broker.
| Parameter | Type | Required | Description |
|---|---|---|---|
x402Version | Integer | Yes | Protocol version, e.g. 2 |
resource | Object | No | Protected-resource description |
resource.url | String | Yes | The URL of the protected resource |
resource.description | String | No | Resource description |
resource.mimeType | String | No | Expected response MIME type |
accepted | Object | Yes | The payment option chosen by the Buyer (selected from the accepts array). Same shape as PaymentRequirements |
payload | Object | Yes | Signed data |
payload.signature | String | Yes | Signature (encoding depends on path; see Permit2Authorization) |
payload.authorization | Object | Conditional | EIP-3009 authorization object — required for exact + EIP-3009 |
payload.permit2Authorization | Object | Conditional | Permit2 authorization object — required for exact + Permit2 / upto |
Constraint: payload.authorization and payload.permit2Authorization must be strictly one-or-the-other, otherwise param_mismatch is returned.
PaymentRequirements#
Used both as an entry of the 402 response accepts array and as paymentPayload.accepted.
| Parameter | Type | Required | Description |
|---|---|---|---|
scheme | String | Yes | "exact" / "upto" / "aggr_deferred" |
network | String | Yes | CAIP-2 network identifier, e.g. eip155:196 |
amount | String | Yes | Amount (atomic-unit string). exact = paid; upto verify = ceiling, upto settle = actual paid |
asset | String | Yes | Token contract address |
payTo | String | Yes | Recipient wallet address |
maxTimeoutSeconds | Integer | No | Max payment-completion timeout (seconds) |
extra | Object | No | Scheme-specific extension (see table below) |
extra fields:
| Field | Applies to | Description |
|---|---|---|
name / version | exact (EIP-3009) | EIP-712 domain fields; required by some tokens |
assetTransferMethod | exact / upto | "permit2" selects the Permit2 path; omitted / "eip3009" selects EIP-3009 |
facilitatorAddress | upto | Facilitator EOA address |
sessionCert | upto (SESSION) | base64-encoded session cert |
signatureScheme | upto | "secp256k1" / "ed25519" |
Authorization#
For the exact + EIP-3009 path.
| Parameter | Type | Required | Description |
|---|---|---|---|
from | String | Yes | Payer wallet address (EOA) |
to | String | Yes | Recipient wallet address (must equal payTo) |
value | String | Yes | Payment amount (atomic units, must equal amount) |
validAfter | String | Yes | Authorization start Unix timestamp |
validBefore | String | Yes | Authorization expiry Unix timestamp |
nonce | String | Yes | 32-byte random nonce (0x hex, anti-replay) |
Permit2Authorization#
For the exact + Permit2 / upto paths.
| Field | Type | Required (exact + Permit2) | Required (upto) | Description |
|---|---|---|---|---|
from | String | ✅ | ✅ | Buyer wallet address (EOA on EOA path; AA on SESSION path) |
permitted.token | String | ✅ | ✅ | Token contract address |
permitted.amount | String | ✅ | ✅ | exact = paid; upto = authorization ceiling |
spender | String | ✅ | ✅ | exact: x402ExactPermit2Proxy; upto: x402UptoPermit2Proxy |
nonce | String | ✅ | ✅ | Permit2 nonce — uint256 decimal string |
deadline | String | ✅ | ✅ | Permit2 deadline Unix timestamp |
witness.to | String | ✅ | ✅ | Recipient address; must equal accepted.payTo |
witness.facilitator | String | — | ✅ | Required for upto; ignored on the exact path |
witness.validAfter | String | ✅ | ✅ | Authorization start Unix timestamp |
payload.signature encoding rules:
| Path | Encoding |
|---|---|
exact + Permit2 (EOA) | 0x-prefixed 65-byte hex, secp256k1 r‖s‖v (EIP-2 low-s) |
upto + EOA | Same as above |
upto + SESSION | base64-encoded Ed25519 signature |
Permit2 on-chain prerequisite: the buyer must have executed approve(MAX_UINT256) on the Permit2 canonical contract 0x000000000022D473030F116dDEE9F6B43aC78BA3, otherwise verify returns insufficient_allowance.
Challenge#
The Challenge object issued by the server, echoed back by the client as-is (used by charge only).
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | Yes | Challenge ID |
realm | String | Yes | Protection space identifier |
method | String | Yes | Always "evm" |
intent | String | Yes | Payment intent: "charge" / "session" |
request | String | Yes | base64url-encoded request parameters |
expires | String | Yes | Expiry time (ISO 8601) |
Split#
A single split entry in the charge splits list — each requires its own signature.
| Parameter | Type | Required | Description |
|---|---|---|---|
from | String | Yes | Payer address (same as primary signature) |
to | String | Yes | Split recipient address |
value | String | Yes | Split amount (base units) |
validAfter | String | Yes | Authorization start Unix timestamp |
validBefore | String | Yes | Authorization expiry Unix timestamp |
nonce | String | Yes | Independent nonce (bytes32) |
signature | String | Yes | 65-byte EIP-712 signature |
Supported networks and tokens#
| Network | Chain Index | Status |
|---|---|---|
| X Layer | 196 | Supported |
Stablecoins supported on X Layer:
| Token | Contract address |
|---|---|
| USDG | 0x4ae46a509f6b1d9056937ba4500cb143933d2dc8 |
| USD₮0 | 0x779ded0c9e1022225f8e0630b35a9b54be713736 |
Error codes#
Error responses use the unified envelope {"code": "<code>", "msg": "<message>", "data": null}.
1. Authentication errors (HTTP 401)#
| Code | Description |
|---|---|
| 50103 | Header OK-ACCESS-KEY cannot be empty |
| 50104 | Header OK-ACCESS-PASSPHRASE cannot be empty |
| 50105 | Header OK-ACCESS-PASSPHRASE is invalid |
| 50106 | Header OK-ACCESS-SIGN cannot be empty |
| 50107 | Header OK-ACCESS-TIMESTAMP cannot be empty |
| 50111 | Invalid OK-ACCESS-KEY |
| 50112 | Invalid OK-ACCESS-TIMESTAMP |
| 50113 | Invalid signature |
2. Request errors#
| Code | HTTP status | Description |
|---|---|---|
| 50011 | 429 | User request rate exceeds the per-endpoint limit |
| 50014 | 400 | Required parameter {param} cannot be empty |
3. Business errors#
| Code | HTTP status | Description |
|---|---|---|
| 50026 | 500 | System error, please retry later |
| 81001 | 200 | {param} parameter error |
| 81004 | 200 | Unsupported chain |
| 80007 | 200 | Risky address |
4. exact / upto verify / settle business fields#
For exact / upto endpoints, failure reasons are returned in data.invalidReason (verify) or data.errorReason (settle / settle/status). Common values:
| Field value | Applies to | Description |
|---|---|---|
insufficient_funds | verify, settle | Payer balance insufficient (legacy) |
insufficient_balance | verify, settle | Balance minus pending is below the required amount |
insufficient_allowance | verify, settle | Buyer's ERC-20 allowance to the Permit2 canonical contract is insufficient |
nonce_already_used | verify, settle | Nonce already used |
expired_authorization | verify, settle | Authorization expired (legacy) |
expired | verify, settle | deadline already past |
not_yet_valid | verify, settle | validAfter > now |
signature_invalid | verify, settle | Signature verification failed (legacy) |
invalid_signature | verify, settle | secp256k1 / Ed25519 verification failed, malformed, or low-s violation |
requirements_mismatch | verify, settle | accepted does not match paymentRequirements (legacy) |
param_mismatch | verify, settle | Missing field / mutual-exclusivity violation / consistency check failed |
unsupported_scheme | verify, settle | Scheme not enabled (Apollo gate is off) |
unsupported_chain | verify, settle | chainIndex not in config |
payer_blocked | verify, settle | Buyer is on the blocklist |
risk_address | verify, settle | Blocked by KYS compliance check (payer or payTo) |
transaction_reverted | settle | On-chain transaction reverted |
chain_unavailable | settle | On-chain RPC unavailable |
onchain_error | verify | Multicall call failed during verify |
onchain_recheck_error | settle | On-chain TOCTOU recheck failed during settle |
settle_busy | settle | Concurrent settle lock conflict on the same (payer, token) |
settle_interrupted | settle | Thread interrupted while waiting for the settle lock |
not_found | settle/status | txHash not found in Broker records |
invalid_permit2_spender | verify (exact + permit2) | spender ≠ x402ExactPermit2Proxy |
permit2_token_mismatch | verify (exact + permit2) | permitted.token ≠ accepted.asset |
permit2_amount_mismatch | verify (exact + permit2) | permitted.amount ≠ paymentRequirements.amount |
invalid_permit2_recipient_mismatch | verify (exact + permit2) | witness.to ≠ accepted.payTo |
permit2_not_yet_valid | verify (exact + permit2) | witness.validAfter > now |
permit2_deadline_expired | verify, settle (permit2) | deadline ≤ now + buffer |
invalid_permit2_signature | verify (exact + permit2) | Permit2 EIP-712 signature verification failed |
invalid_spender | verify (upto) | spender ≠ x402UptoPermit2Proxy |
invalid_amount | verify (upto) | upto ceiling ≤ 0 |
upto_signature_route_conflict | verify (upto) | sessionCert and signatureScheme mutual-exclusivity violation |
upto_facilitator_mismatch | verify, settle (upto) | witness.facilitator missing / invalid / not in allowlist / does not match accepted.extra.facilitatorAddress |
upto_invalid_settlement_amount | settle (upto) | paymentRequirements.amount is negative or malformed |
upto_settlement_exceeds_amount | settle (upto) | settle amount > permit ceiling |
invalid_eoa_signature | settle (upto + EOA) | EOA signature not 0x-prefixed or not 65 bytes |
tee_sign_failed | settle (upto + SESSION) | TEE signMsg(eip712Hash) co-sign failed |
account_resolve_error | settle (upto + SESSION) | AA account resolution failed |
intent_submit_failed | settle (upto) | Intent Framework submission failed |
5. charge business errors#
| Code | Name | Description |
|---|---|---|
| 8000 | SERVICE_ERROR | Internal API service error |
| 70000 | invalid_params | Missing required field or invalid format |
| 70001 | unsupported_chain | Chain not in supported list |
| 70002 | payer_blocked | Payer is blocklisted |
| 70003 | invalid_credential | source missing, or txHash already used |
| 70004 | invalid_signature | Signature verification failed |
| 70005 | split_sum_exceeds_total | Split total >= primary amount |
| 70006 | split_count_exceeded | Split count > 10 |
| 70007 | tx_not_confirmed | Transaction not confirmed on-chain |
| 70009 | challenge_invalid | Challenge does not exist or has expired |
- Choose your schemeAuthenticationexact Scheme1. /api/v6/pay/x402/supportedRequest parametersResponse parametersRequest exampleResponse example2. /api/v6/pay/x402/verifyRequest parametersResponse parametersRequest example — exact + EIP-3009Request example — exact + Permit2Response example — verification passedResponse example — verification failed3. /api/v6/pay/x402/settleRequest parametersResponse parametersRequest example — exact + EIP-3009 sync settleRequest example — exact + Permit2 async settleResponse example — exact sync settle success (syncSettle=true)Response example — exact async settle (syncSettle=false)Response example — exact wait timeout (syncSettle=true)Response example — settle failure4. /api/v6/pay/x402/settle/statusRequest parametersResponse parametersRequest exampleResponse example — query success (exact)Response example — query success (upto)Response example — transaction not foundupto SchemeDual signature paths5. /api/v6/pay/x402/verify — scheme=uptoRequest example — upto + EOARequest example — upto + SESSION (OKX agentic wallet)Response6. /api/v6/pay/x402/settle — scheme=uptoSettle response matrixRequest exampleResponse example — normal broadcastResponse example — zero-settleResponse example — failurecharge Scheme7. /api/v6/pay/mpp/charge/settleRequest parametersResponse parametersRequest exampleResponse exampleRequest example — with splits8. /api/v6/pay/mpp/charge/verifyHashRequest parametersResponse parametersRequest exampleResponse exampleCommon data structuresPaymentPayloadPaymentRequirementsAuthorizationPermit2AuthorizationChallengeSplitSupported networks and tokensError codes1. Authentication errors (HTTP 401)2. Request errors3. Business errors4. exact / upto verify / settle business fields5. charge business errors
