Cổng thông tin nhà phát triển
Chủ đề

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.

Dimensionexact (EIP-3009)exact (Permit2)uptocharge
RecipientSingleSingleSingleSingle / multi (≤10)
Settlement timingSync / asyncSync / asyncAsync (default pending)Sync only
Asset transferEIP-3009 transferWithAuthorizationPermit2 canonical → x402ExactPermit2ProxyPermit2 canonical → x402UptoPermit2ProxyEIP-3009
Token compatibilityEIP-3009 tokens onlyAny ERC-20Any ERC-20EIP-3009 tokens only
Amount semanticssigned = paidsigned = paidsigned = ceiling, paid ≤ ceilingsigned = paid
Integration guideexact pathexact pathupto pathcharge path
  • Base URL: https://web3.okx.com
  • exact / upto path prefix: /api/v6/pay/x402 (the four endpoints — verify / settle / supported / settle/status — are shared and routed automatically by payload content)
  • charge path prefix: /api/v6/pay/mpp/charge
  • Network: X Layer (chainId 196, CAIP-2 identifier eip155:196)

Authentication#

All endpoints require API Key authentication. The following headers must be provided:

HeaderRequiredDescription
OK-ACCESS-KEYYesAPI Key
OK-ACCESS-SIGNYesRequest signature
OK-ACCESS-PASSPHRASEYesAPI passphrase
OK-ACCESS-TIMESTAMPYesISO 8601 timestamp
Content-TypeYesapplication/json for POST requests

All responses use a unified envelope:

json
{
  "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.extra either omits assetTransferMethod or sets it to eip3009.
  • Permit2 path: populate payload.permit2Authorization; accepted.extra.assetTransferMethod="permit2", and paymentRequirements.extra is 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#

GET
/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#

ParameterTypeDescription
kindsArray<SupportedKind>Supported payment-type list
kinds[].x402VersionIntegerProtocol version, e.g. 2
kinds[].schemeStringSettlement scheme: exact / aggr_deferred / upto
kinds[].networkStringCAIP-2 network identifier, e.g. eip155:196
kinds[].extraObjectScheme-specific extension config (see table below)
extensionsArray<String>Supported extension identifiers
signersObjectCAIP-2 wildcard → array of signer addresses

kinds[].extra fields:

FieldApplies toDescription
assetTransferMethodexact / upto"permit2" means settlement goes through Permit2 canonical + Proxy contract. When omitted on exact, native EIP-3009 is used
facilitatorAddressuptoFacilitator EOA address. The buyer must put the same address in witness.facilitator
signatureSchemesuptoSignature algorithms currently supported by the facilitator. ["ed25519"] when EOA path is gated off; ["ed25519", "secp256k1"] after the EOA path is enabled

Request example#

bash
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#

json
{
  "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#

POST
/api/v6/pay/x402/verify

Validate the Buyer's signed payment authorization. No on-chain transaction is executed.

Request parameters#

ParameterTypeRequiredDescription
x402VersionIntegerYesProtocol version, e.g. 2
paymentPayloadObjectYesThe payment payload the client sends with the protected request. See PaymentPayload
paymentRequirementsObjectYesThe Seller-defined payment requirements. See PaymentRequirements

Constraints:

  • paymentPayload.accepted.scheme must match paymentRequirements.scheme; allowed values are "exact" and "upto".
  • paymentPayload.payload.authorization and paymentPayload.payload.permit2Authorization must be strictly one-or-the-other:
    • exact + EIP-3009: populate authorization
    • exact + Permit2: populate permit2Authorization, with accepted.scheme="exact"
    • upto: populate permit2Authorization, with accepted.scheme="upto" and witness.facilitator required

Response parameters#

ParameterTypeDescription
isValidBooleantrue = passed, false = failed
invalidReasonStringMachine-readable invalid reason (returned on failure)
invalidMessageStringHuman-readable invalid message (returned on failure)
payerStringPayer wallet address

Request example — exact + EIP-3009#

bash
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#

bash
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.witness only contains to / validAfter here — no facilitator (the DTO has the field, but the exact path ignores it).
  • spender must equal Apollo x402.exact.permit2.proxy.address, otherwise invalid_permit2_spender is returned.
  • permit2Authorization.permitted.amount must equal paymentRequirements.amount (signed = paid).

Response example — verification passed#

json
{
  "code": "0",
  "msg": "success",
  "data": {
    "isValid": true,
    "invalidReason": null,
    "invalidMessage": null,
    "payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d"
  }
}

Response example — verification failed#

json
{
  "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#

POST
/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's transferWithAuthorization directly.
  • exact + Permit2: calls x402ExactPermit2Proxy.settle. The buyer must have already approve(MAX_UINT256)d the ERC-20 to the Permit2 canonical contract.
  • upto: calls x402UptoPermit2Proxy.settle. The Facilitator co-signs (HSM) before broadcasting.

Request parameters#

ParameterTypeRequiredDescription
x402VersionIntegerYesProtocol version, e.g. 2
paymentPayloadObjectYesSame as verify
paymentRequirementsObjectYesSame as verify. For upto, amount is the actual paid amount and must be ≤ permit2Authorization.permitted.amount (the ceiling)
syncSettleBooleanNoOKX extension. true = wait for on-chain confirmation (poll until timeout); false (default) = async broadcast. upto ignores this field and is always async

Response parameters#

ParameterTypeDescription
successBooleanWhether settlement succeeded
errorReasonStringMachine-readable failure reason (returned on failure)
errorMessageStringHuman-readable failure message (returned on failure)
payerStringPayer wallet address
transactionStringOn-chain transaction hash
networkStringCAIP-2 network identifier
statusStringOKX extension. Settlement status — see table below
amountStringOKX extension. upto returns the actual settled amount (atomic units); zero-settle returns "0". Not returned for exact / exact + permit2

status matrix:

PathsyncSettleResultstatustransactionamount
exact (EIP-3009 / Permit2)false (default)BroadcastpendingtxHash
exact (EIP-3009 / Permit2)trueConfirmed on-chainsuccesstxHash
exact (EIP-3009 / Permit2)trueWait timeouttimeouttxHash
exact (EIP-3009 / Permit2)Verify / simulation / on-chain failurefailed""
upto— (ignored)Zero-settlesuccessnull"0"
upto— (ignored)BroadcastpendingtxHashActual amount
upto— (ignored)Verify / simulation / on-chain failurefailed""

Request example — exact + EIP-3009 sync settle#

bash
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)#

json
{
  "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)#

json
{
  "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)#

json
{
  "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#

json
{
  "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#

GET
/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#

ParameterLocationTypeRequiredDescription
txHashqueryStringYesOn-chain transaction hash

Response parameters#

ParameterTypeDescription
successBooleanWhether the query succeeded (false if txHash not found; also false if settleStatus = FAILED)
errorReasonStringMachine-readable failure reason (DB error_reason field)
errorMessageStringHuman-readable failure message
payerStringPayer wallet address
transactionStringOn-chain transaction hash
networkStringCAIP-2 network identifier
statusStringCurrent settlement status: pending / success / failed
amountStringOKX extension. upto returns the actual settled amount (atomic units); null for exact / exact + permit2

Request example#

bash
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)#

json
{
  "code": "0",
  "msg": "success",
  "data": {
    "success": true,
    "payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
    "transaction": "0x4f46ed8eac92ddbccfb56a88ff827db3616c7beb191adabbeeded901340bd7d5",
    "network": "eip155:196",
    "status": "success",
    "amount": null
  }
}

Response example — query success (upto)#

json
{
  "code": "0",
  "msg": "success",
  "data": {
    "success": true,
    "payer": "0xcb30ed083ad246b126a3aa1f414b44346e83e67d",
    "transaction": "0xabc...",
    "network": "eip155:196",
    "status": "success",
    "amount": "1234000"
  }
}

Response example — transaction not found#

json
{
  "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:

PathWalletSignature algorithmsessionCertsignatureScheme
EOAMetaMask / Rabby / Coinbase Wallet, etc.secp256k1 (EIP-712)omittedomitted / "secp256k1"
SESSIONOKX agentic walletEd25519 (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#

POST
/api/v6/pay/x402/verify

The body shape is identical to exact + Permit2. Differences:

Fieldupto requirement
paymentPayload.accepted.schemeMust be "upto"
paymentRequirements.schemeMust be "upto"
paymentPayload.payload.permit2Authorization.witness.facilitatorRequired and must be in the facilitator allowlist (Apollo x402.upto.facilitator.address)
paymentPayload.accepted.extra.facilitatorAddressMust equal witness.facilitator (case-insensitive)
paymentPayload.accepted.extra.sessionCertRequired on SESSION path, omitted on EOA path
paymentPayload.accepted.extra.signatureSchemeOptional: "secp256k1" / "ed25519". Auto-detected from sessionCert when omitted
paymentPayload.payload.permit2Authorization.spenderMust equal Apollo x402.upto.permit2.proxy.address (different from the exact + permit2 proxy); otherwise invalid_spender
paymentPayload.payload.permit2Authorization.permitted.amountAuthorization 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#

bash
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:

json
{
  "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#

POST
/api/v6/pay/x402/settle

The body shape is identical to verify. Differences:

  • paymentRequirements.amount is the actual paid amount, with 0 ≤ 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 returns transaction=null / status=success / amount="0".
  • syncSettle is ignored; upto always returns pending asynchronously.

Settle response matrix#

ScenariosuccessstatustransactionamounterrorReason
Zero-settletruesuccessnull"0"
Normal broadcast (async)truependingtxHashactual amount
settle amount > ceilingfalsefailed""upto_settlement_exceeds_amount
Facilitator field invalidfalsefailed""upto_facilitator_mismatch
EOA signature format invalidfalsefailed""invalid_eoa_signature
TEE co-sign failed (SESSION)falsefailed""tee_sign_failed
Intent submission failedfalsefailed""intent_submit_failed
Same (payer, nonce) replayedtrueprevious statusprevious txHash or ""DB settle_amount or "0"

Request example#

bash
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#

json
{
  "code": "0",
  "msg": "success",
  "data": {
    "success": true,
    "payer": "0xbuyer...",
    "transaction": "0xabc...",
    "network": "eip155:196",
    "status": "pending",
    "amount": "1234000"
  }
}

Response example — zero-settle#

json
{
  "code": "0",
  "msg": "success",
  "data": {
    "success": true,
    "payer": "0xbuyer...",
    "transaction": null,
    "network": "eip155:196",
    "status": "success",
    "amount": "0"
  }
}

Response example — failure#

json
{
  "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#

POST
/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#

ParameterTypeRequiredDescription
challengeObjectYesThe Challenge object issued by the server (echo back as-is). See Challenge
payloadObjectYesEVM payment receipt
payload.typeStringYesAlways "transaction"
payload.authorizationObjectYesEIP-3009 authorization object
payload.authorization.typeStringYesAlways "eip-3009"
payload.authorization.fromStringYesPayer wallet address
payload.authorization.toStringYesRecipient wallet address
payload.authorization.valueStringYesPayment amount (base units)
payload.authorization.validAfterStringYesAuthorization start Unix timestamp
payload.authorization.validBeforeStringYesAuthorization expiry Unix timestamp
payload.authorization.nonceStringYesRandom bytes32, unique per authorization
payload.authorization.signatureStringYes65-byte EIP-712 signature (r‖s‖v)
payload.authorization.splitsArrayNoSplits list (max 10). See Split
sourceStringNoPayer DID (did:pkh:eip155:196:0x...)

Response parameters#

ParameterTypeDescription
methodStringAlways "evm"
referenceStringOn-chain transaction hash (0x-prefixed)
statusStringAlways "success"
timestampStringRFC 3339 settlement time
chainIdIntegerSettlement chain ID, e.g. 196
challengeIdStringChallenge ID, for client correlation
externalIdStringEchoed merchant order ID from the Challenge request

Request example#

bash
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#

json
{
  "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#

bash
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#

POST
/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#

ParameterTypeRequiredDescription
challengeObjectYesThe Challenge object issued by the server (echo back as-is). See Challenge
payloadObjectYesPayment receipt
payload.typeStringYesAlways "hash"
payload.hashStringYesThe on-chain transaction hash already broadcast by the client
sourceStringYesPayer DID (did:pkh:eip155:196:0x...)

Response parameters#

ParameterTypeDescription
methodStringAlways "evm"
referenceStringOn-chain transaction hash (0x-prefixed)
statusStringAlways "success"
timestampStringRFC 3339 confirmation time
chainIdIntegerChain ID, e.g. 196
challengeIdStringChallenge ID, for client correlation
externalIdStringEchoed merchant order ID from the Challenge request

Request example#

bash
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#

json
{
  "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.

ParameterTypeRequiredDescription
x402VersionIntegerYesProtocol version, e.g. 2
resourceObjectNoProtected-resource description
resource.urlStringYesThe URL of the protected resource
resource.descriptionStringNoResource description
resource.mimeTypeStringNoExpected response MIME type
acceptedObjectYesThe payment option chosen by the Buyer (selected from the accepts array). Same shape as PaymentRequirements
payloadObjectYesSigned data
payload.signatureStringYesSignature (encoding depends on path; see Permit2Authorization)
payload.authorizationObjectConditionalEIP-3009 authorization object — required for exact + EIP-3009
payload.permit2AuthorizationObjectConditionalPermit2 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.

ParameterTypeRequiredDescription
schemeStringYes"exact" / "upto" / "aggr_deferred"
networkStringYesCAIP-2 network identifier, e.g. eip155:196
amountStringYesAmount (atomic-unit string). exact = paid; upto verify = ceiling, upto settle = actual paid
assetStringYesToken contract address
payToStringYesRecipient wallet address
maxTimeoutSecondsIntegerNoMax payment-completion timeout (seconds)
extraObjectNoScheme-specific extension (see table below)

extra fields:

FieldApplies toDescription
name / versionexact (EIP-3009)EIP-712 domain fields; required by some tokens
assetTransferMethodexact / upto"permit2" selects the Permit2 path; omitted / "eip3009" selects EIP-3009
facilitatorAddressuptoFacilitator EOA address
sessionCertupto (SESSION)base64-encoded session cert
signatureSchemeupto"secp256k1" / "ed25519"

Authorization#

For the exact + EIP-3009 path.

ParameterTypeRequiredDescription
fromStringYesPayer wallet address (EOA)
toStringYesRecipient wallet address (must equal payTo)
valueStringYesPayment amount (atomic units, must equal amount)
validAfterStringYesAuthorization start Unix timestamp
validBeforeStringYesAuthorization expiry Unix timestamp
nonceStringYes32-byte random nonce (0x hex, anti-replay)

Permit2Authorization#

For the exact + Permit2 / upto paths.

FieldTypeRequired (exact + Permit2)Required (upto)Description
fromStringBuyer wallet address (EOA on EOA path; AA on SESSION path)
permitted.tokenStringToken contract address
permitted.amountStringexact = paid; upto = authorization ceiling
spenderStringexact: x402ExactPermit2Proxy; upto: x402UptoPermit2Proxy
nonceStringPermit2 nonce — uint256 decimal string
deadlineStringPermit2 deadline Unix timestamp
witness.toStringRecipient address; must equal accepted.payTo
witness.facilitatorStringRequired for upto; ignored on the exact path
witness.validAfterStringAuthorization start Unix timestamp

payload.signature encoding rules:

PathEncoding
exact + Permit2 (EOA)0x-prefixed 65-byte hex, secp256k1 r‖s‖v (EIP-2 low-s)
upto + EOASame as above
upto + SESSIONbase64-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).

ParameterTypeRequiredDescription
idStringYesChallenge ID
realmStringYesProtection space identifier
methodStringYesAlways "evm"
intentStringYesPayment intent: "charge" / "session"
requestStringYesbase64url-encoded request parameters
expiresStringYesExpiry time (ISO 8601)

Split#

A single split entry in the charge splits list — each requires its own signature.

ParameterTypeRequiredDescription
fromStringYesPayer address (same as primary signature)
toStringYesSplit recipient address
valueStringYesSplit amount (base units)
validAfterStringYesAuthorization start Unix timestamp
validBeforeStringYesAuthorization expiry Unix timestamp
nonceStringYesIndependent nonce (bytes32)
signatureStringYes65-byte EIP-712 signature

Supported networks and tokens#

NetworkChain IndexStatus
X Layer196Supported

Stablecoins supported on X Layer:

TokenContract address
USDG0x4ae46a509f6b1d9056937ba4500cb143933d2dc8
USD₮00x779ded0c9e1022225f8e0630b35a9b54be713736

Error codes#

Error responses use the unified envelope {"code": "<code>", "msg": "<message>", "data": null}.

1. Authentication errors (HTTP 401)#

CodeDescription
50103Header OK-ACCESS-KEY cannot be empty
50104Header OK-ACCESS-PASSPHRASE cannot be empty
50105Header OK-ACCESS-PASSPHRASE is invalid
50106Header OK-ACCESS-SIGN cannot be empty
50107Header OK-ACCESS-TIMESTAMP cannot be empty
50111Invalid OK-ACCESS-KEY
50112Invalid OK-ACCESS-TIMESTAMP
50113Invalid signature

2. Request errors#

CodeHTTP statusDescription
50011429User request rate exceeds the per-endpoint limit
50014400Required parameter {param} cannot be empty

3. Business errors#

CodeHTTP statusDescription
50026500System error, please retry later
81001200{param} parameter error
81004200Unsupported chain
80007200Risky 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 valueApplies toDescription
insufficient_fundsverify, settlePayer balance insufficient (legacy)
insufficient_balanceverify, settleBalance minus pending is below the required amount
insufficient_allowanceverify, settleBuyer's ERC-20 allowance to the Permit2 canonical contract is insufficient
nonce_already_usedverify, settleNonce already used
expired_authorizationverify, settleAuthorization expired (legacy)
expiredverify, settledeadline already past
not_yet_validverify, settlevalidAfter > now
signature_invalidverify, settleSignature verification failed (legacy)
invalid_signatureverify, settlesecp256k1 / Ed25519 verification failed, malformed, or low-s violation
requirements_mismatchverify, settleaccepted does not match paymentRequirements (legacy)
param_mismatchverify, settleMissing field / mutual-exclusivity violation / consistency check failed
unsupported_schemeverify, settleScheme not enabled (Apollo gate is off)
unsupported_chainverify, settlechainIndex not in config
payer_blockedverify, settleBuyer is on the blocklist
risk_addressverify, settleBlocked by KYS compliance check (payer or payTo)
transaction_revertedsettleOn-chain transaction reverted
chain_unavailablesettleOn-chain RPC unavailable
onchain_errorverifyMulticall call failed during verify
onchain_recheck_errorsettleOn-chain TOCTOU recheck failed during settle
settle_busysettleConcurrent settle lock conflict on the same (payer, token)
settle_interruptedsettleThread interrupted while waiting for the settle lock
not_foundsettle/statustxHash not found in Broker records
invalid_permit2_spenderverify (exact + permit2)spender ≠ x402ExactPermit2Proxy
permit2_token_mismatchverify (exact + permit2)permitted.token ≠ accepted.asset
permit2_amount_mismatchverify (exact + permit2)permitted.amount ≠ paymentRequirements.amount
invalid_permit2_recipient_mismatchverify (exact + permit2)witness.to ≠ accepted.payTo
permit2_not_yet_validverify (exact + permit2)witness.validAfter > now
permit2_deadline_expiredverify, settle (permit2)deadline ≤ now + buffer
invalid_permit2_signatureverify (exact + permit2)Permit2 EIP-712 signature verification failed
invalid_spenderverify (upto)spender ≠ x402UptoPermit2Proxy
invalid_amountverify (upto)upto ceiling ≤ 0
upto_signature_route_conflictverify (upto)sessionCert and signatureScheme mutual-exclusivity violation
upto_facilitator_mismatchverify, settle (upto)witness.facilitator missing / invalid / not in allowlist / does not match accepted.extra.facilitatorAddress
upto_invalid_settlement_amountsettle (upto)paymentRequirements.amount is negative or malformed
upto_settlement_exceeds_amountsettle (upto)settle amount > permit ceiling
invalid_eoa_signaturesettle (upto + EOA)EOA signature not 0x-prefixed or not 65 bytes
tee_sign_failedsettle (upto + SESSION)TEE signMsg(eip712Hash) co-sign failed
account_resolve_errorsettle (upto + SESSION)AA account resolution failed
intent_submit_failedsettle (upto)Intent Framework submission failed

5. charge business errors#

CodeNameDescription
8000SERVICE_ERRORInternal API service error
70000invalid_paramsMissing required field or invalid format
70001unsupported_chainChain not in supported list
70002payer_blockedPayer is blocklisted
70003invalid_credentialsource missing, or txHash already used
70004invalid_signatureSignature verification failed
70005split_sum_exceeds_totalSplit total >= primary amount
70006split_count_exceededSplit count > 10
70007tx_not_confirmedTransaction not confirmed on-chain
70009challenge_invalidChallenge does not exist or has expired
Table of contents