HTTP API — Pay-as-you-go (Session)#

A streaming payment channel built on an on-chain Escrow contract. The Buyer opens the channel and deposits funds; the Seller accumulates consumption via off-chain EIP-712 Vouchers; the channel finally settles on-chain and refunds the unused balance.

  • Base URL: https://web3.okx.com
  • Path prefix: /api/v6/pay/mpp/session
  • Network: X Layer (chainId 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.


1. /api/v6/pay/mpp/session/open#

POST
/api/v6/pay/mpp/session/open

Open a payment channel. Two modes are supported:

  • transaction mode: server-side opens — the Buyer provides an EIP-3009 authorization, and the Broker submits the on-chain openChannel transaction on their behalf
  • hash mode: the client opens the channel themselves and provides the broadcast transaction hash for the Broker to verify

Request parameters#

ParameterTypeRequiredDescription
challengeObjectYesThe Challenge object issued by the server (echo back as-is). See Challenge
payloadObjectYesPayment receipt
payload.actionStringYesAlways "open"
payload.typeStringYes"transaction" (server-side opens) or "hash" (client-side already opened)
payload.channelIdStringYesChannel ID (bytes32, client-precomputed)
payload.authorizationObjectConditionalRequired when type="transaction"; the EIP-3009 authorization object
payload.authorization.typeStringConditionalAlways "eip-3009"
payload.authorization.fromStringConditionalPayer address
payload.authorization.toStringConditionalEscrow contract address
payload.authorization.valueStringConditionalDeposit amount (base units)
payload.authorization.validAfterStringConditionalAuthorization start Unix timestamp
payload.authorization.validBeforeStringConditionalAuthorization expiry Unix timestamp
payload.authorization.nonceStringConditionalRandom bytes32
payload.signatureStringConditionalRequired when type="transaction"; EIP-3009 signature (65 bytes)
payload.hashStringConditionalRequired when type="hash"; on-chain open transaction hash
payload.saltStringYesRandom bytes32, used to compute channelId
payload.authorizedSignerStringNoDelegated signer address; defaults to 0x0000...0000
sourceStringConditionalRequired when type="hash"; payer DID (did:pkh:eip155:196:0x...)

Response parameters#

ParameterTypeDescription
methodStringAlways "evm"
intentStringAlways "session"
statusStringAlways "success"
timestampStringRFC 3339 response time
channelIdStringChannel ID (bytes32)
chainIdIntegerEVM chain ID, e.g. 196
referenceStringOn-chain transaction hash (returned in transaction mode)
depositStringCurrently known on-chain deposit (base units)

Request example — hash mode (client-side opens)#

bash
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/session/open' \
--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: 2023-10-18T12:21:41.274Z' \
--data '{
  "challenge": {
    "id": "kM9xPqWvT2nJrHsY4aDfEb",
    "realm": "api.llm-service.com",
    "method": "evm",
    "intent": "session",
    "request": "eyJ...",
    "expires": "2026-04-01T12:05:00Z"
  },
  "source": "did:pkh:eip155:196:0xaabbccddee11223344556677889900aabbccddee",
  "payload": {
    "action": "open",
    "type": "hash",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "hash": "0x9f8e7d6c5b4a39281700abcdef1234567890abcdef1234567890abcdef123456",
    "signature": "0xabcdef1234567890...",
    "authorizedSigner": "0x742d35cc6634c0532925a3b844bc9e7595f8fe00",
    "salt": "0xaaaa1234bbbb5678cccc9012dddd3456eeee7890ffff1234aaaa5678bbbb9012"
  }
}'

Request example — transaction mode (server-side opens)#

bash
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/session/open' \
--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: 2023-10-18T12:21:41.274Z' \
--data '{
  "challenge": {
    "id": "kM9xPqWvT2nJrHsY4aDfEb",
    "realm": "api.llm-service.com",
    "method": "evm",
    "intent": "session",
    "request": "eyJ...",
    "expires": "2026-04-01T12:05:00Z"
  },
  "payload": {
    "action": "open",
    "type": "transaction",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "authorization": {
      "type": "eip-3009",
      "from": "0xaabbccddee11223344556677889900aabbccddee",
      "to": "0x1234567890abcdef1234567890abcdef12345678",
      "value": "10000000",
      "validAfter": "0",
      "validBefore": "1743523500",
      "nonce": "0xaaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888"
    },
    "signature": "0xabcdef...eip3009sig",
    "authorizedSigner": "0x742d35cc6634c0532925a3b844bc9e7595f8fe00",
    "salt": "0xaaaa1234bbbb5678cccc9012dddd3456eeee7890ffff1234aaaa5678bbbb9012"
  }
}'

Response example#

json
{
  "code": "0",
  "msg": "",
  "data": {
    "method": "evm",
    "intent": "session",
    "status": "success",
    "timestamp": "2026-04-01T12:04:30Z",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "chainId": 196,
    "reference": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
    "deposit": "10000000"
  }
}

2. /api/v6/pay/mpp/session/topUp#

POST
/api/v6/pay/mpp/session/topUp

Top up the deposit on an already-open channel. Supports transaction mode (server-side tops up) and hash mode (client-side already topped up).

Request parameters#

ParameterTypeRequiredDescription
challengeObjectNoServer-issued Challenge object. See Challenge
payloadObjectYesPayment receipt
payload.actionStringYesAlways "topUp"
payload.typeStringYes"transaction" (server-side tops up) or "hash" (client-side already topped up)
payload.channelIdStringYesChannel ID (bytes32)
payload.authorizationObjectConditionalRequired when type="transaction"; EIP-3009 authorization object
payload.authorization.typeStringConditionalAlways "eip-3009"
payload.authorization.fromStringConditionalPayer address
payload.authorization.toStringConditionalEscrow contract address
payload.authorization.valueStringConditionalTop-up amount (base units)
payload.authorization.validAfterStringConditionalAuthorization start Unix timestamp
payload.authorization.validBeforeStringConditionalAuthorization expiry Unix timestamp
payload.authorization.nonceStringConditionalRandom bytes32
payload.signatureStringConditionalRequired when type="transaction"; EIP-3009 signature (65 bytes)
payload.hashStringConditionalRequired when type="hash"; on-chain topUp transaction hash
payload.additionalDepositStringYesThe amount being added this time (base units)
payload.topUpSaltStringYesRandom bytes32, required for topUp
sourceStringConditionalRequired when type="hash"; payer DID (did:pkh:eip155:196:0x...)

Response parameters#

ParameterTypeDescription
methodStringAlways "evm"
intentStringAlways "session"
statusStringAlways "success"
timestampStringRFC 3339 response time
channelIdStringChannel ID (bytes32)
chainIdIntegerEVM chain ID, e.g. 196
referenceStringOn-chain transaction hash (returned in transaction mode)
depositStringCurrently known total on-chain deposit (base units)

Request example — transaction mode (server-side tops up)#

bash
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/session/topUp' \
--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: 2023-10-18T12:21:41.274Z' \
--data '{
  "payload": {
    "action": "topUp",
    "type": "transaction",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "authorization": {
      "type": "eip-3009",
      "from": "0xaabbccddee11223344556677889900aabbccddee",
      "to": "0x1234567890abcdef1234567890abcdef12345678",
      "value": "5000000",
      "validAfter": "0",
      "validBefore": "1743523500",
      "nonce": "0xcccc1234dddd5678eeee9012ffff3456aaaa7890bbbb1234cccc5678dddd9012"
    },
    "signature": "0xefgh....",
    "additionalDeposit": "5000000",
    "topUpSalt": "0xdddd1234eeee5678ffff9012aaaa3456bbbb7890cccc1234dddd5678eeee9012"
  }
}'

Request example — hash mode (client-side already topped up)#

bash
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/session/topUp' \
--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: 2023-10-18T12:21:41.274Z' \
--data '{
  "challenge": {
    "id": "qB3wErTyU7iOpAsD9fGhJk",
    "realm": "api.llm-service.com",
    "method": "evm",
    "intent": "session",
    "request": "eyJhbW91bnQiOiIxMDAi...",
    "expires": "2026-04-01T12:05:00Z"
  },
  "source": "did:pkh:eip155:196:0xaabbccddee11223344556677889900aabbccddee",
  "payload": {
    "action": "topUp",
    "type": "hash",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "hash": "0x9f8e7d6c5b4a3928170fabcdef1234567890abcdef1234567890abcdef123456",
    "additionalDeposit": "5000000",
    "topUpSalt": "0xdddd1234eeee5678ffff9012aaaa3456bbbb7890cccc1234dddd5678eeee9012"
  }
}'

Response example#

json
{
  "code": "0",
  "msg": "",
  "data": {
    "method": "evm",
    "intent": "session",
    "status": "success",
    "timestamp": "2026-04-01T12:04:30Z",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "chainId": 196,
    "reference": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
    "deposit": "15000000"
  }
}

3. /api/v6/pay/mpp/session/settle#

POST
/api/v6/pay/mpp/session/settle

Mid-stream settle — the server submits the Voucher on-chain on behalf of the merchant. The merchant (payee) signs an EIP-712 SettleAuthorization with their own key; the Voucher signature (signed by payer or authorizedSigner) is uploaded with the request. The contract calls settleWithAuthorization.

Request parameters#

ParameterTypeRequiredDescription
payloadObjectYesSettle payload
payload.actionStringNo"settle"
payload.channelIdStringYesChannel ID (bytes32 hex, 0x-prefixed)
payload.cumulativeAmountStringYesCumulative amount to settle (token base units, uint128 decimal string). If <= settledOnChain the server short-circuits without going on-chain
payload.voucherSignatureStringYesEIP-712 Voucher signature (65-byte r‖s‖v hex, 0x-prefixed). Signer = channel.authorizedSigner (or channel.payer if not set)
payload.payeeSignatureStringYesEIP-712 SettleAuthorization signature (65 bytes). Signer = channel.payee
payload.nonceStringYesuint256 decimal string, randomly generated by the merchant. The (payee, channelId, nonce) tuple is unique on-chain; reuse is rejected with NonceAlreadyUsed
payload.deadlineStringYesSignature expiry Unix seconds. Rejected when current server time > deadline

Response parameters#

ParameterTypeDescription
methodStringAlways "evm"
intentStringAlways "session"
statusStringAlways "success"
timestampStringRFC 3339 response time
channelIdStringChannel ID (bytes32)
chainIdIntegerEVM chain ID, e.g. 196
referenceStringOn-chain transaction hash
depositStringCurrently known on-chain deposit (base units)

Request example#

bash
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/session/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: 2023-10-18T12:21:41.274Z' \
--data '{
  "payload": {
    "action": "settle",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "cumulativeAmount": "250000",
    "voucherSignature": "0x4a5b6c7d8e9fa0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b501",
    "payeeSignature": "0x11223344556677889900aabbccddeeff00112233445566778899aabbccddeeff1b",
    "nonce": "17890324512398000000000000000000000000000000000000000000000000000001",
    "deadline": "1745500000"
  }
}'

Response example#

json
{
  "code": "0",
  "msg": "",
  "data": {
    "method": "evm",
    "intent": "session",
    "status": "success",
    "timestamp": "2026-04-01T12:04:30Z",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "chainId": 196,
    "reference": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
    "deposit": "10000000"
  }
}

4. /api/v6/pay/mpp/session/close#

POST
/api/v6/pay/mpp/session/close

Close the channel and finalize settlement. Uses max(client amount, server's highest held Voucher) to protect merchant revenue. After settlement, the Escrow contract refunds the unused deposit to the payer.

When cumulativeAmount <= settledOnChain, the waiver branch is taken (no new Voucher signature required).

Request parameters#

ParameterTypeRequiredDescription
payloadObjectYesClose payload
payload.actionStringNo"close"
payload.channelIdStringYesChannel ID (bytes32 hex, 0x-prefixed)
payload.cumulativeAmountStringYesFinal cumulative amount (token base units, uint128 decimal string). When <= settledOnChain the waiver branch is taken
payload.voucherSignatureStringConditionalEIP-712 Voucher signature (65-byte r‖s‖v hex). Required on the normal branch; can be empty "" on the waiver branch
payload.payeeSignatureStringYesEIP-712 CloseAuthorization signature (65 bytes). Signer = channel.payee
payload.nonceStringYesuint256 decimal string, randomly generated by the merchant. Shares the same (payee, channelId, nonce) used-set with SettleAuthorization — cannot be reused across types
payload.deadlineStringYesSignature expiry Unix seconds. Rejected when current server time > deadline

Response parameters#

ParameterTypeDescription
methodStringAlways "evm"
intentStringAlways "session"
statusStringAlways "success"
timestampStringRFC 3339 response time
channelIdStringChannel ID (bytes32)
chainIdIntegerEVM chain ID, e.g. 196
referenceStringOn-chain transaction hash
depositStringCurrently known on-chain deposit (base units)

Request example#

bash
curl --location --request POST 'https://web3.okx.com/api/v6/pay/mpp/session/close' \
--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: 2023-10-18T12:21:41.274Z' \
--data '{
  "payload": {
    "action": "close",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "cumulativeAmount": "500000",
    "voucherSignature": "0x4a5b6c7d8e9fa0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b501",
    "payeeSignature": "0x22334455667788990011aabbccddeeff00112233445566778899aabbccddeeff1c",
    "nonce": "17890324512398000000000000000000000000000000000000000000000000000002",
    "deadline": "1745500600"
  }
}'

Response example#

json
{
  "code": "0",
  "msg": "",
  "data": {
    "method": "evm",
    "intent": "session",
    "status": "success",
    "timestamp": "2026-04-01T12:04:30Z",
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "chainId": 196,
    "reference": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
    "deposit": "10000000"
  }
}

5. /api/v6/pay/mpp/session/status#

GET
/api/v6/pay/mpp/session/status

Query the current state of a payment channel (read-only).

Request parameters#

ParameterLocationTypeRequiredDescription
channelIdqueryStringYesChannel ID (bytes32 hex)

Response parameters#

ParameterTypeDescription
channelIdStringChannel ID (bytes32)
payerStringPayer address
payeeStringPayee address
tokenStringERC-20 token contract address
depositStringTotal deposit amount (base units)
settledOnChainStringAmount already settled on-chain (only updated after settle is called)
sessionStatusStringChannel state: OPEN / CLOSING / CLOSED
remainingBalanceStringRemaining balance (deposit - cumulativeAmount)

Request example#

bash
curl --location --request GET 'https://web3.okx.com/api/v6/pay/mpp/session/status?channelId=0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f' \
--header 'OK-ACCESS-KEY: 37c541a1-****-****-****-10fe7a038418' \
--header 'OK-ACCESS-SIGN: leaV********3uw=' \
--header 'OK-ACCESS-PASSPHRASE: 1****6' \
--header 'OK-ACCESS-TIMESTAMP: 2023-10-18T12:21:41.274Z'

Response example#

json
{
  "code": "0",
  "msg": "",
  "data": {
    "channelId": "0x6d0f4fdf1f2f6a1f6c1b0fbd6a7d5c2c0a8d3d7b1f6a9c1b3e2d4a5b6c7d8e9f",
    "payer": "0xaabbccddee11223344556677889900aabbccddee",
    "payee": "0x742d35Cc6634c0532925a3b844bC9e7595F8fE00",
    "token": "0xA8CE8aee21bC2A48a5EF670afCc9274C7bbbC035",
    "deposit": "10000000",
    "settledOnChain": "100000",
    "sessionStatus": "OPEN",
    "remainingBalance": "9750000"
  }
}

Common data structures#

Challenge#

The Challenge object issued by the server, echoed back by the client as-is.

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

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#

CodeNameDescription
8000SERVICE_ERRORInternal API service error
70000invalid_paramsMissing required field or invalid format
70001unsupported_chainChain is not in the 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
70008channel_closeOn-chain contract channel is already closed
70009challenge_invalidChallenge does not exist or has expired
70010channel_not_foundchannelId does not exist
70011grace_period_too_shortEscrow contract grace period under 10 minutes — channel open rejected
70012amount_exceeds_depositcumulativeAmount exceeds the channel's deposit balance
70013voucher_delta_too_smallVoucher increment below minVoucherDelta
70014channel_closingChannel is in CLOSING state and won't accept new Vouchers