Skip to content

s402 Wire Format Specification

Version: 1  ·  Status: Draft  ·  Date: March 2026

This document defines the s402 wire format — the exact encoding, field definitions, validation rules, and error semantics for the s402 HTTP 402 payment protocol. It is the authoritative reference for any implementation in any language.

The TypeScript reference implementation lives at github.com/s402-protocol/core. Machine-readable conformance test vectors ship in the npm package (161 vectors across 13 files).

1. Terminology

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

TermDefinition
Resource ServerThe HTTP server that hosts paid resources and returns 402 responses.
ClientThe HTTP client (typically an AI agent) that requests resources and submits payments.
FacilitatorA service that verifies and settles payment transactions on-chain on behalf of the Resource Server. Optional — direct settlement bypasses the Facilitator.
RequirementsThe JSON object sent by the server in a 402 response describing what payment is needed.
PayloadThe JSON object sent by the client containing a signed payment transaction.
SchemeA payment pattern (exact, upto, prepaid, stream, escrow, unlock) defining the on-chain lifecycle.
Base unitsThe smallest denomination of a currency (e.g., MIST for SUI, wei for ETH, lamports for SOL).

2. Protocol Overview

s402 uses the HTTP 402 status code to negotiate payment between a client and a server. The protocol has three phases:

Phase 1: Discovery
  Client  ─── GET /resource ──────────────>  Server
  Client  <── 402 + payment-required ──────  Server

Phase 2: Payment
  Client  ─── GET /resource + x-payment ──>  Server
  Server  ─── payload + requirements ──────>  Facilitator
  Server  <── settlement result ───────────  Facilitator

Phase 3: Delivery
  Client  <── 200 + payment-response ──────  Server

The presence of s402Version in the decoded requirements JSON distinguishes s402 from x402 and other 402 protocols.

3. Transport

3.1 Header Transport (default)

s402 uses three HTTP headers. All header names are lowercase per RFC 9113 §8.2.1.

HeaderDirectionContent
payment-requiredServer → ClientBase64-encoded Payment Requirements JSON
x-paymentClient → ServerBase64-encoded Payment Payload JSON
payment-responseServer → ClientBase64-encoded Settlement Response JSON

These header names are identical to x402 V1 for wire compatibility.

3.2 Encoding

Header transport uses Unicode-safe base64 encoding:

  1. JSON-serialize the object: JSON.stringify(object)
  2. UTF-8 encode the string to bytes
  3. Base64 encode the bytes using standard base64 (RFC 4648 §4)

For ASCII-only content (the common case), this produces identical output to applying base64 directly to the JSON string. The UTF-8 step ensures that Unicode characters in the extensions field or error messages survive the round-trip.

Implementations MUST decode in the reverse order: base64 decode → UTF-8 decode → JSON parse.

3.3 Header Size Limit

Implementations SHOULD enforce a maximum header size of 65,536 bytes (64 KiB) on decoded headers. Headers exceeding this limit SHOULD be rejected before base64 decoding.

This is a defense-in-depth measure. Most HTTP servers enforce smaller limits (Node.js: 16 KiB, Cloudflare Workers: 128 KiB). A wire format library should not rely on runtime enforcement alone.

3.4 Body Transport (large payloads)

When a payment payload exceeds header size limits (e.g., complex DeFi PTBs exceeding 128 KiB after base64), implementations MAY use body transport instead.

  • Content-Type: application/s402+json
  • Encoding: Raw JSON (no base64)
  • Size limits: Set by the application (Express default: 100 KiB, Nginx default: 1 MiB)

Implementations MUST apply the same validation and key-stripping rules to body-transported objects as to header-transported objects.

To detect which transport a request uses, check:

  1. If Content-Type includes application/s402+json → body transport
  2. If the x-payment header is present → header transport
  3. Otherwise → unknown

3.5 Protocol Detection

To determine whether a 402 response uses s402 or x402:

  1. Read the payment-required header
  2. Base64 decode and JSON parse
  3. If the decoded object contains s402Versions402
  4. If it contains x402Versionx402
  5. Otherwise → unknown

4. Payment Requirements

The Payment Requirements object is sent by the Resource Server in the payment-required header of a 402 response. It describes what payment the server accepts.

4.1 Required Fields

FieldTypeConstraintsDescription
s402VersionstringMUST be "1"Protocol version. Distinguishes s402 from x402.
acceptsstring[]Non-empty array. Each entry MUST be a string.Payment schemes the server accepts. SHOULD include "exact" for x402 compatibility.
networkstringNon-empty. No control characters (U+0000–U+001F, U+007F).Network identifier. RECOMMENDED format: CAIP-2 style (e.g., "sui:mainnet", "eip155:8453", "solana:mainnet-beta").
assetstringNon-empty. No control characters.Asset/coin type identifier. Format is chain-specific and opaque to s402 (e.g., "0x2::sui::SUI", "0xA0b8..." for ERC-20).
amountstringCanonical non-negative integer. See §4.3.Payment amount in base units.
payTostringNon-empty. No control characters.Recipient address. Format is chain-specific and opaque to s402.

4.2 Optional Fields

FieldTypeConstraintsDescription
facilitatorUrlstringValid URL. Protocol MUST be https: or http:. No control characters.URL of the Facilitator service. Omit for direct settlement.
expiresAtnumberPositive finite number.Unix timestamp in milliseconds. Facilitators MUST reject requirements after this time.
protocolFeeBpsnumberInteger, 0–10000.Protocol fee in basis points. Advisory only — the authoritative fee is set by the Facilitator's on-chain config.
protocolFeeAddressstringNon-empty. No control characters.Address that receives the protocol fee. Advisory only.
receiptRequiredbooleanWhether the server requires an on-chain receipt.
settlementModestring"facilitator" or "direct"Settlement mode preference.
mandateobjectSee §4.5.AP2 mandate requirements.
uptoobjectSee §4.6.Upto-specific parameters. Present when accepts includes "upto".
settlementOverridesobjectSee §4.6.Settlement overrides for upto scheme (server provides actual amount).
streamobjectSee §4.7.Stream-specific parameters. Present when accepts includes "stream".
escrowobjectSee §4.8.Escrow-specific parameters. Present when accepts includes "escrow".
unlockobjectSee §4.9.Unlock-specific parameters. Present when accepts includes "unlock".
prepaidobjectSee §4.10.Prepaid-specific parameters. Present when accepts includes "prepaid".
extensionsobjectOpaque key-value bag.Forward-compatible extensibility. Consumers MUST treat extension values as untrusted input. See §4.10.

4.3 Amount Format

The amount field MUST be a canonical non-negative integer string:

  • MUST match the regular expression ^(0|[1-9][0-9]*)$
  • MUST NOT have leading zeros (except the string "0" itself)
  • MUST NOT contain decimals, negative signs, or whitespace
  • MAY be arbitrarily large (no upper magnitude bound at the wire format level)

Examples of valid amounts: "0", "1", "1000000", "18446744073709551616"

Examples of invalid amounts: "-1", "007", "1.5", "abc", "1,000", ""

Chain-specific magnitude bounds

The wire format does not enforce chain-specific magnitude limits (e.g., u64 for Sui, u256 for Ethereum). Chain adapters SHOULD validate that amounts fit within their chain's native integer type before constructing transactions.

4.4 Control Character Rejection

The following fields MUST NOT contain ASCII control characters (U+0000–U+001F) or the DEL character (U+007F):

  • network
  • asset
  • payTo
  • facilitatorUrl
  • protocolFeeAddress

Control characters in these fields could enable HTTP header injection (CRLF) or log injection (null bytes). Implementations MUST reject requirements containing control characters in these fields with error code INVALID_PAYLOAD.

4.5 Mandate Sub-Object

Used for AP2 (Agent Payment Authorization) mandate requirements.

FieldTypeRequiredConstraintsDescription
requiredbooleanYesWhether a mandate is required (true) or optional (false).
minPerTxstringNoAmount format (§4.3)Minimum per-transaction spending limit the mandate must allow.
coinTypestringNoCoin type the mandate must authorize. Must match asset.

4.6 Upto Sub-Object

Required when accepts includes "upto".

FieldTypeRequiredConstraintsDescription
maxAmountstringYesAmount format (§4.3)Maximum authorized amount in base units. Client deposits this; actual may be less.
settlementDeadlineMsstringYesAmount format (§4.3). Must be in the future.Deadline for settlement (ms since epoch). After this, payer can reclaim via expire().
estimatedAmountstringNoAmount format (§4.3). Must be ≤ maxAmount.Server's estimated cost (advisory). Helps clients set a tight settlementCeiling.
usageReportUrlstringNoURL where the client can query usage/metering data (informational).

Settlement Overrides Sub-Object (used at settle-time):

FieldTypeRequiredConstraintsDescription
actualAmountstringYesAmount format (§4.3). Must be ≤ maxAmount.Actual amount to settle, based on observed usage.

4.7 Stream Sub-Object

Required when accepts includes "stream".

FieldTypeRequiredConstraintsDescription
ratePerSecondstringYesAmount format (§4.3)Rate in base units per second.
budgetCapstringYesAmount format (§4.3)Maximum total budget in base units.
minDepositstringYesAmount format (§4.3)Minimum initial deposit in base units.
streamSetupUrlstringNoURL for stream status checks.

4.8 Escrow Sub-Object

Required when accepts includes "escrow".

FieldTypeRequiredConstraintsDescription
sellerstringYesSeller/payee address.
deadlineMsstringYesAmount format (§4.3)Escrow deadline as Unix timestamp in milliseconds.
arbiterstringNoArbiter address for dispute resolution.

4.9 Unlock Sub-Object

Required when accepts includes "unlock".

FieldTypeRequiredConstraintsDescription
encryptionIdstringYesEncryption key identifier for key servers.
encryptedContentIdstringYesIdentifier for the encrypted content (e.g., Walrus blob ID, IPFS CID).
encryptionServiceIdstringYesIdentifier for the encryption service or module (e.g., Sui package ID, EVM contract address).

4.10 Prepaid Sub-Object

Required when accepts includes "prepaid".

FieldTypeRequiredConstraintsDescription
ratePerCallstringYesAmount format (§4.3)Maximum base units per API call (rate cap).
minDepositstringYesAmount format (§4.3)Minimum deposit amount in base units.
withdrawalDelayMsstringYesAmount format (§4.3). Value MUST be ≥ 60000 (1 min) and ≤ 604800000 (7 days).Withdrawal delay in milliseconds. Agent must wait this long after the last provider claim before withdrawing remaining funds.
maxCallsstringNoAmount format (§4.3)Maximum number of API calls. Omit for unlimited.
providerPubkeystringNoProvider's Ed25519 public key (hex, 32 bytes). Enables v0.2 signed receipt mode.
disputeWindowMsstringNoDispute window in milliseconds. Min 60000, max 86400000.

Pairing invariant: providerPubkey and disputeWindowMs MUST both be present (v0.2 mode) or both absent (v0.1 mode). Implementations MUST reject requirements where only one is present.

4.11 Extensions Field

The extensions field is an opaque key-value bag for forward-compatible extensibility. Implementations:

  • MUST pass extensions through without content validation
  • MUST NOT use extensions for security-critical fields
  • MUST treat extension values as untrusted input

Scheme implementations that consume specific extension keys SHOULD validate those keys independently.

The extensions field is intended as a proving ground for features that may be promoted to first-class typed fields in a future protocol version.

5. Payment Payload

The Payment Payload is sent by the client in the x-payment header (or request body for body transport). It contains a signed transaction for the selected payment scheme.

5.1 Common Fields

FieldTypeRequiredConstraintsDescription
s402VersionstringNoIf present, MUST be "1"Protocol version. Optional on payloads for x402 interop.
schemestringYesOne of: "exact", "upto", "stream", "escrow", "unlock", "prepaid"The payment scheme being used. MUST be in the server's accepts array.
payloadobjectYesScheme-specific inner fields. See below.The scheme-specific payment data.

5.2 Payload Inner Fields by Scheme

Exact (scheme: "exact"):

FieldTypeRequiredDescription
payload.transactionstringYesBase64-encoded signed transaction bytes.
payload.signaturestringYesBase64-encoded signature.

Upto (scheme: "upto"):

FieldTypeRequiredDescription
payload.transactionstringYesBase64-encoded signed deposit transaction (creates UptoDeposit on-chain).
payload.signaturestringYesBase64-encoded signature.
payload.maxAmountstringYesMaximum authorized amount. Must match requirements.upto.maxAmount.
payload.settlementCeilingstringNoClient-chosen settlement ceiling (on-chain enforced). Must be ≤ maxAmount.

Stream (scheme: "stream"):

FieldTypeRequiredDescription
payload.transactionstringYesBase64-encoded stream creation transaction.
payload.signaturestringYesBase64-encoded signature.

Escrow (scheme: "escrow"):

FieldTypeRequiredDescription
payload.transactionstringYesBase64-encoded escrow creation transaction.
payload.signaturestringYesBase64-encoded signature.

Unlock (scheme: "unlock"):

FieldTypeRequiredDescription
payload.transactionstringYesBase64-encoded escrow creation transaction (TX1 of two-stage flow).
payload.signaturestringYesBase64-encoded signature.
payload.encryptionIdstringYesEncryption key identifier. Must match requirements.

Prepaid (scheme: "prepaid"):

FieldTypeRequiredDescription
payload.transactionstringYesBase64-encoded deposit transaction.
payload.signaturestringYesBase64-encoded signature.
payload.ratePerCallstringYesCommitted rate per call. Must match requirements.
payload.maxCallsstringNoCommitted max calls cap. Must match requirements if present.

6. Settlement Response

The Settlement Response is sent by the server in the payment-response header of the 200 response after successful payment, or in a non-200 response on failure.

FieldTypeRequiredConstraintsDescription
successbooleanYesWhether settlement succeeded.
txDigeststringNoOn-chain transaction digest/hash.
receiptIdstringNoOn-chain receipt object ID.
finalityMsnumberNoFinite number.Time to finality in milliseconds.
actualAmountstringNoActual amount settled in base units (upto scheme).
depositIdstringNoUptoDeposit object ID (upto scheme).
streamIdstringNoStream object ID (stream scheme).
escrowIdstringNoEscrow object ID (escrow scheme).
balanceIdstringNoPrepaidBalance object ID (prepaid scheme).
errorstringNoHuman-readable error message (on failure).
errorCodestringNoOne of the codes in §8.Machine-readable error code (on failure).

7. Signed Usage Receipts

For the prepaid scheme (v0.2 mode), providers sign each API response with a receipt header. This enables cryptographic fraud proofs.

7.1 Receipt Header

HeaderDirectionContent
X-S402-ReceiptServer → ClientColon-separated receipt fields

7.2 Receipt Format

v2:<base64(signature)>:<callNumber>:<timestampMs>:<base64(responseHash)>
PartTypeConstraintsDescription
VersionstringMUST be "v2"Receipt format version.
Signaturebase64Decoded length MUST be exactly 64 bytesEd25519 signature over the BCS-encoded receipt message.
Call numberinteger stringPositive (> 0)Sequential call number, 1-indexed.
Timestampinteger stringPositive (> 0)Unix timestamp in milliseconds when the response was generated.
Response hashbase64Decoded length MUST be exactly 32 bytesSHA-256 hash of the response body.

Implementations MUST reject receipts where:

  • The header is empty
  • The number of colon-separated parts is not exactly 5
  • The version is not "v2"
  • The call number or timestamp is not a valid positive integer
  • The signature does not decode to exactly 64 bytes
  • The response hash does not decode to exactly 32 bytes

8. Error Codes

Every s402 error carries three fields: code (machine-readable), retryable (boolean), and suggestedAction (human-readable guidance). This design enables autonomous agents to handle errors programmatically.

CodeRetryableSuggested Action
INSUFFICIENT_BALANCENoTop up wallet balance or try with a smaller amount
MANDATE_EXPIREDNoRequest a new mandate from the delegator
MANDATE_LIMIT_EXCEEDEDNoRequest mandate increase or split across transactions
STREAM_DEPLETEDYesTop up the stream deposit
ESCROW_DEADLINE_PASSEDNoCreate a new escrow with a later deadline
UNLOCK_DECRYPTION_FAILEDYesRe-request decryption key with a fresh session key
FINALITY_TIMEOUTYesTransaction submitted but not confirmed — retry finality check
FACILITATOR_UNAVAILABLEYesFall back to direct settlement if signer is available
INVALID_PAYLOADNoCheck payload format and re-sign the transaction
SCHEME_NOT_SUPPORTEDNoUse the "exact" scheme (always supported for x402 compat)
NETWORK_MISMATCHNoEnsure client and server are on the same network
SIGNATURE_INVALIDNoRe-sign the transaction with the correct keypair
REQUIREMENTS_EXPIREDYesRe-fetch payment requirements from the server
VERIFICATION_FAILEDNoCheck payment amount and transaction structure
SETTLEMENT_FAILEDYesTransient RPC failure during settlement — retry in a few seconds

9. Discovery

Servers MAY advertise s402 support at /.well-known/s402.json:

json
{
  "s402Version": "1",
  "schemes": ["exact", "upto", "stream", "escrow", "unlock", "prepaid"],
  "networks": ["sui:mainnet"],
  "assets": ["0x2::sui::SUI"],
  "facilitatorUrl": "https://facilitator.example.com",
  "directSettlement": true,
  "mandateSupport": true,
  "protocolFeeBps": 50,
  "protocolFeeAddress": "0x..."
}
FieldTypeRequiredDescription
s402VersionstringYesMUST be "1".
schemesstring[]YesSupported payment schemes.
networksstring[]YesSupported network identifiers.
assetsstring[]YesSupported asset/coin type identifiers.
facilitatorUrlstringNoDefault Facilitator URL.
directSettlementbooleanYesWhether direct settlement (no Facilitator) is supported.
mandateSupportbooleanYesWhether AP2 mandates are supported.
protocolFeeBpsnumberYesDefault protocol fee in basis points (0–10000).
protocolFeeAddressstringNoAddress that receives the protocol fee.

10. Key Stripping (Trust Boundary)

All three decode functions (requirements, payload, settlement response) MUST strip unknown top-level keys from decoded objects. Only the keys listed in this specification SHOULD survive decoding.

This is a defense-in-depth measure at the HTTP trust boundary — it prevents untrusted fields from propagating into application logic.

10.1 Known Requirements Keys

s402Version, accepts, network, asset, amount, payTo, facilitatorUrl, mandate, protocolFeeBps, protocolFeeAddress, receiptRequired, settlementMode, expiresAt, upto, stream, escrow, unlock, prepaid, settlementOverrides, extensions

Sub-object known keys:

  • mandate: required, minPerTx, coinType
  • upto: maxAmount, settlementDeadlineMs, estimatedAmount, usageReportUrl
  • settlementOverrides: actualAmount
  • stream: ratePerSecond, budgetCap, minDeposit, streamSetupUrl
  • escrow: seller, arbiter, deadlineMs
  • unlock: encryptionId, encryptedContentId, encryptionServiceId
  • prepaid: ratePerCall, maxCalls, minDeposit, withdrawalDelayMs, providerPubkey, disputeWindowMs

10.2 Known Payload Keys

Top-level: s402Version, scheme, payload

Inner payload keys per scheme:

  • exact, stream, escrow: transaction, signature
  • upto: transaction, signature, maxAmount, settlementCeiling
  • unlock: transaction, signature, encryptionId
  • prepaid: transaction, signature, ratePerCall, maxCalls

10.3 Known Settlement Response Keys

success, txDigest, receiptId, finalityMs, actualAmount, depositId, streamId, escrowId, balanceId, error, errorCode

11. x402 Compatibility

s402 is wire-compatible with Coinbase's x402 V1 protocol on the exact scheme.

11.1 Header Names

s402 uses the same HTTP header names as x402 V1. An x402 V1 client sending an exact payment can interact with an s402 server without modification.

x402 V2 renamed the client header to payment-signature. Servers that need to accept x402 V2 clients SHOULD also check the payment-signature header.

11.2 Protocol Discrimination

The s402Version field in the decoded JSON distinguishes s402 from x402. x402 uses x402Version (an integer) instead.

11.3 Conversion

Implementations MAY provide bidirectional conversion between x402 and s402 formats:

  • x402 → s402: Map x402's scheme to s402's accepts array. Use amount (V2) or maxAmountRequired (V1) for the amount field.
  • s402 → x402: Only the exact scheme has an x402 equivalent. Other schemes (stream, escrow, unlock, prepaid) are s402-only.

Conversion MUST validate the facilitatorUrl field using the same protocol-only check (§4.2) to prevent SSRF via dangerous URL schemes.

12. Security Considerations

12.1 HTTPS Required

s402 payment data (requirements, payloads, settlement responses) travels in HTTP headers as base64-encoded JSON. Without TLS, this data is visible to any network observer. All production deployments MUST use HTTPS.

12.2 Requirements Expiration

Servers SHOULD set expiresAt on payment requirements to prevent replay of stale 402 responses. Facilitators MUST reject requirements where Date.now() > expiresAt.

12.3 Facilitator URL Validation

The facilitatorUrl field is validated for protocol only (https: or http:). Implementations that fetch the Facilitator URL MUST apply their own hostname and IP address restrictions (block RFC 1918 private addresses, link-local 169.254.x.x, loopback, cloud metadata endpoints) to prevent SSRF attacks.

12.4 Extensions Trust Boundary

The extensions field is an opaque bag. Implementations MUST NOT trust data in extensions for security-critical decisions. Security-critical data SHOULD use first-class typed fields with explicit validation.

12.5 Concurrent Payment Deduplication

Facilitators SHOULD deduplicate concurrent identical payment requests to prevent double resource access. Deduplication keys SHOULD be derived from the scheme name and transaction/signature fields, not from JSON serialization (which is not canonical).

12.6 Key Ordering in JSON

JSON key ordering is not guaranteed by the JSON specification. Implementations that serialize s402 objects MUST NOT depend on specific key ordering for correctness. However, for conformance vector compatibility, implementations SHOULD preserve insertion order as described in the conformance test guide.

13. Conformance

An implementation is s402-conformant if it:

  1. Correctly encodes and decodes all three message types (requirements, payload, settlement response) using the encoding specified in §3.2
  2. Validates all required fields per §4.1, §5.1, and §6
  3. Rejects malformed input with the appropriate error code from §8
  4. Strips unknown keys on decode per §10
  5. Passes the 161 machine-readable conformance test vectors shipped in the s402 npm package

The conformance vectors cover: encode, decode, body transport, x402 compat normalization, receipt format/parse, settlement verification, validation rejection, key stripping, and roundtrip identity. See the Conformance Vectors guide for the vector format and implementation instructions.


This specification is maintained at s402-protocol.org/specification and github.com/s402-protocol/core. Contributions and corrections are welcome via GitHub issues or pull requests.

Released under the Apache 2.0 License.