This document defines the protocol contract that all PayNode SDKs must follow.
This is a protocol document, not a JavaScript implementation guide. Wire format, verification rules, compatibility behavior, and security requirements defined here apply to all SDKs, including JS, Python, Java, and Go.
This specification covers two distinct protocol layers:
- Part I: Direct x402 Protocol (Upstream-Aligned): A stateless payment handshake between an Agent and a Merchant.
- Part II: PayNode Market Proxy Protocol: A trust-based flow where a Merchant verifies requests signed by the PayNode Market Proxy.
These layers have different trust assumptions, headers, and verification rules. They must not be confused.
The canonical payment loop is:
- Merchant returns
402 Payment Requiredchallenge. - Agent chooses a payment option and produces a payment proof.
- Agent retries the original request with the proof.
- Merchant verifies proof and returns the protected response.
When a protected resource requires payment, the merchant must respond with:
- HTTP status
402. - JSON response body.
PAYMENT-REQUIREDheader (Base64-encoded version of the JSON body).X-402-Order-Idheader (Merchant-generated unique request identifier).
(Reference: meta/fixtures/wire/base/payment_required.json)
{
"x402Version": 2,
"error": "Payment Required by PayNode",
"resource": {
"url": "https://api.merchant.com/v1/tools",
"description": "Premium AI reasoning engine",
"mimeType": "application/json"
},
"orderId": "merchant-order-123",
"accepts": [
{
"scheme": "exact",
"type": "eip3009",
"network": "eip155:8453",
"amount": "100000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xMerchantWallet",
"maxTimeoutSeconds": 3600,
"extra": { "name": "USDC", "version": "2" }
},
{
"scheme": "exact",
"type": "onchain",
"network": "eip155:8453",
"amount": "100000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xMerchantWallet",
"maxTimeoutSeconds": 3600,
"router": "0xPayNodeRouter"
}
]
}x402VersionMUST be2.orderIdMUST be present in the root of the body and in theX-402-Order-Idheader.schemeMUST be"exact"for this version.networkMUST use CAIP-2 identifiers (eip155:<chainId>).amountMUST be an integer string in base units (e.g. 100000 for 0.1 USDC with 6 decimals).
The agent retries the original request with:
PAYMENT-SIGNATUREheader (Base64-encoded JSON envelope).X-402-Order-Idheader (matching the challenge).
(Reference: meta/fixtures/wire/base/payment_signature_eip3009.json)
{
"x402Version": 2,
"resource": { "url": "https://api.merchant.com/v1/tools" },
"accepted": {
"scheme": "exact",
"type": "eip3009",
"network": "eip155:8453",
"amount": "100000",
"asset": "0xToken",
"payTo": "0xMerchantWallet"
},
"payload": { ... }
}Payload MUST contain signature and authorization fields. (Reference: meta/fixtures/crypto/eip3009_transfer_with_authorization.json).
Payload MUST contain txHash. (Reference: meta/fixtures/wire/base/payment_signature_onchain.json).
- Amount: MUST be >= required amount.
- Token: MUST be on the merchant-configured whitelist.
- Destination: MUST match the merchant address.
- Order ID: MUST match the request session.
- Replay: MUST NOT have been processed before (nonce or txHash check).
In this flow, the Merchant trusts the PayNode Market Proxy. The Proxy handles the 402 handshake and sends a verified request to the Merchant over HTTPS, signed with a shared HMAC secret.
The Proxy includes these headers in its request to the Merchant:
X-PayNode-Signature: HMAC-SHA256 signature.X-PayNode-Timestamp: Unix timestamp (milliseconds).X-PayNode-Request-Id: Unique identifier for the proxied request.X-PayNode-Discovery: (Optional)truefor discovery probes.
The signature MUST be calculated as:
HMAC-SHA256(sharedSecret, requestId + ":" + timestamp)
(Reference: meta/fixtures/crypto/market_proxy_hmac.json)
- Check
timestampis within allowed drift (e.g. ±5 minutes). - Use constant-time comparison to verify
X-PayNode-Signature. - Check
X-PayNode-Request-Idfor replays.
All SDKs MUST use these exact transformation rules:
- Order ID Hashing:
keccak256(utf8(orderId))->bytes32. (Reference:meta/fixtures/crypto/order_id_hash.json). - EIP-3009 Domain: Standard USDC/ERC-20 domain fields (name, version, chainId, verifyingContract).
- Market Proxy Input:
${requestId}:${timestamp}.
For an SDK to be compliant, it MUST:
- Pass all
meta/fixtures/crypto/golden vectors. - Correctly handle the canonical headers:
PAYMENT-REQUIRED,PAYMENT-SIGNATURE,PAYMENT-RESPONSE,X-402-Order-Id. - Implement
Part Ifor direct x402. - (Optional) Implement
Part IIfor PayNode Market integration.
PayNode-specific implementations MAY include a _paynode field in the JSON envelope for implementation-specific metadata (e.g., sdkVersion). This field is an extension and NOT part of the upstream core protocol.
Example:
{
"_paynode": {
"sdkVersion": "2.3.0",
"type": "eip3009",
"orderId": "merchant-order-123"
}
}- MULTI_LANGUAGE_BASELINE.md lists the tasks remaining for full cross-language stabilization.
- fixtures/README.md describes the shared test assets.