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 (196 vectors across 14 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 402 leg is an x402 V2 PaymentRequired envelope. What distinguishes an s402-profile 402 from a plain x402 one is the presence of extensions.s402 — and a 402 without it is still payable by an s402 client.

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 x402 V2 PaymentRequired envelope
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. The 402 document is x402 V2's, so an unmodified x402 V2 client reads it as well.

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 is an s402-profile 402 or a plain x402 one:

  1. Read the payment-required header
  2. Base64 decode and JSON parse
  3. If the decoded object contains extensions.s402 → s402
  4. Otherwise, if it contains x402Version → x402
  5. Otherwise → unknown

This names the presence of s402's extensions, never "not for us": both are x402 V2 envelopes, and an s402 client pays either.

4. Payment Requirements ​

The 402 document is an x402 V2 PaymentRequired envelope, sent by the Resource Server in the payment-required header (and body) of a 402 response.

json
{
  "x402Version": 2,
  "error": "Payment Required",
  "resource": { "url": "https://api.example.com/data", "mimeType": "application/json" },
  "accepts": [
    { "scheme": "exact", "network": "sui:mainnet", "asset": "0x2::sui::SUI",
      "amount": "1000000", "payTo": "0x00…01", "maxTimeoutSeconds": 60, "extra": {} }
  ],
  "extensions": { "s402": { "version": "2" } }
}

Three levels, and which level a field sits at is the whole design:

  • the envelope — x402's, byte-compatible with its decoder
  • each accepts[] entry — one offered scheme, x402's PaymentRequirements
  • extra and extensions.s402 — everything s402 adds, in the slots x402 leaves for it

4.1 Envelope Fields ​

FieldTypeRequiredConstraintsDescription
x402VersionnumberYesMUST be 2Envelope version.
resourceobjectYesMUST carry a string url. Fields per §4.1.2.What is being paid for.
acceptsobject[]YesNon-empty. Each entry per §4.2.One entry per offered scheme. exact MUST be listed first whenever it is offered — an x402 client pays the first entry it has a handler for.
errorstringNo—Human-readable reason, surfaced by x402 clients.
extensionsobjectNoOpaque bagEnvelope-level extensions. s402's own live under the s402 key; see §4.1.1.

4.1.1 extensions.s402 ​

FieldTypeRequiredConstraintsDescription
versionstringNoIf present, MUST be "2"s402 wire version. Its presence marks the 402 as an s402-profile 402.
mandateobjectNoSee §4.5.AP2 mandate requirements. Envelope-level: a mandate authorizes the AGENT, not one price line, so it cannot differ per entry.

A 402 carrying no extensions.s402 is a plain x402 402. Implementations MUST decode it and MUST NOT treat its absence as an error.

4.1.2 resource fields ​

x402 V2's ResourceInfo. The bounds are upstream's, not s402's, and an emitted 402 that breaks one is a document the pinned @x402/core decoder refuses to parse.

FieldTypeRequiredConstraintsDescription
urlstringYesMUST be a string on decode. MUST be non-empty on emission.URL of the resource being paid for.
descriptionstringNo—Human-readable description.
mimeTypestringNo—Media type of the paid response.
serviceNamestringNo1–32 characters, printable ASCII (U+0020–U+007E) only.Display name of the service.
tagsstring[]NoAt most 5 entries; each 1–32 characters, printable ASCII only.Discovery tags.
iconUrlstringNoAt most 2048 characters.Icon for the service.

The asymmetry on url is deliberate: emission is held to upstream's schema because that is what the interop claim is about, while a decoder has no business refusing a peer's payable 402 over empty metadata.

4.2 accepts[] Entry Fields ​

Each entry is an x402 V2 PaymentRequirements.

FieldTypeRequiredConstraintsDescription
schemestringYesNon-empty. No control characters.The single scheme this entry offers. Implementations MUST NOT reject an unrecognized scheme name — a client SKIPS an offer it cannot pay.
networkstringYesMUST be CAIP-2: at least 3 characters and containing ":" (e.g., "sui:mainnet", "eip155:8453"). No control characters.Network identifier. x402 V2 requires CAIP-2, so an emitted 402 carrying anything else is one the pinned @x402/core decoder refuses; implementations MUST reject it with INVALID_PAYLOAD. The single exception is read-only intake: a document lifted out of a retired flat shape (§11.3) predates the rule — x402 V1's own schema is a non-empty string — and ADR-013 makes reading it an obligation. Such a document MUST be readable and MUST NOT be re-emitted.
assetstringYesNon-empty. No control characters.Asset/coin type identifier. Chain-specific, opaque to s402.
amountstringYesCanonical non-negative integer. See §4.3.Payment amount in base units.
payTostringYesNon-empty. No control characters.Recipient address. Chain-specific, opaque to s402.
maxTimeoutSecondsnumberNoPositive finite number. Zero and negatives MUST be rejected with INVALID_PAYLOAD.Seconds the facilitator will wait before rejecting. Emitters SHOULD always send it; 60 is the default when a requirement does not name one. Upstream's schema is positive, and an offer good for zero seconds was never payable.
extraobjectNoOpaque bagScheme-specific requirement fields. s402's own are listed in §4.2.1. Keys s402 does not name MUST be preserved on decode — this bag is x402's and open by specification.

The fields in §4.2.1 are defined only for the six s402 schemes. On an entry naming any other scheme, extra is that scheme's own bag: implementations MUST NOT validate it against §4.2.1, MUST NOT lift fields out of it, and MUST carry it through unchanged. One unreadable offer must not make a whole 402 unreadable — the offers a client can pay are validated to the letter, and the rest are skipped.

4.2.1 s402 fields inside an entry's extra ​

FieldTypeConstraintsDescription
facilitatorUrlstringValid URL. Protocol MUST be https: or http:. No control characters. No embedded credentials.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.
receiptRequiredboolean—Whether the server requires an on-chain receipt.
settlementModestring"facilitator" or "direct"Settlement mode preference.
uptoobjectSee §4.6.Upto-specific parameters. Present when scheme is "upto".
settlementOverridesobjectSee §4.6.Settlement overrides for the upto scheme.
streamobjectSee §4.7.Stream-specific parameters. Present when scheme is "stream".
escrowobjectSee §4.8.Escrow-specific parameters. Present when scheme is "escrow".
unlockobjectSee §4.9.Unlock-specific parameters. Present when scheme is "unlock".
prepaidobjectSee §4.10.Prepaid-specific parameters. Present when scheme is "prepaid".
extensionsobjectOpaque bagPer-requirement forward-compatible extensibility. Consumers MUST treat extension values as untrusted input.

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

  • scheme
  • 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
requiredbooleanYes—Whether a mandate is required (true) or optional (false).
minPerTxstringNoAmount format (§4.3)Minimum per-transaction spending limit the mandate must allow.
coinTypestringNo—Coin 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.
usageReportUrlstringNo—URL 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.
streamSetupUrlstringNo—URL for stream status checks.

4.8 Escrow Sub-Object ​

Required when accepts includes "escrow".

FieldTypeRequiredConstraintsDescription
sellerstringYes—Seller/payee address.
deadlineMsstringYesAmount format (§4.3)Escrow deadline as Unix timestamp in milliseconds.
arbiterstringNo—Arbiter address for dispute resolution.

4.9 Unlock Sub-Object ​

Required when accepts includes "unlock".

FieldTypeRequiredConstraintsDescription
packageIdstringYes—Move package implementing pay_and_mint and the seal_approve policy; also the Seal identity namespace.
keyServersarrayYesNon-emptyKey-server set the seller encrypted to. Each entry is an object with objectId (string, on-chain registered key server) and weight (number).
thresholdnumberYesInteger >= 1Threshold t in the t-of-n threshold encryption.
contentDigeststringNosha256-<base64url>Commitment to the plaintext, for off-chain/reputational evidence.

No identity field appears in requirements, and this is structural rather than an omission. The Seal identity is receiptId || nonce, where receiptId is the object ID of the UnlockReceipt minted by pay_and_mint. That receipt does not exist when the server writes the 402 — it is created by the buyer's transaction, in response to this very response. The identity therefore travels in the unlock fulfillment, not here.

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.
providerPubkeystringNo—Provider's Ed25519 public key (hex, 32 bytes). Enables v0.2 signed receipt mode.
disputeWindowMsstringNo—Dispute 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.
networkstringNoNon-empty. No control characters. SHOULD be the CAIP-2 network of the entry it answers (§4.2).Names which accepts[] entry this payment answers. A decoder MUST carry it through (§10.2); a gate uses it to disambiguate two offers that share a scheme. Omitting it is valid — a payload identifies its offer on scheme alone whenever the route's entries for that scheme are the same contract.
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 signed pay_and_mint transaction. Single-transaction scheme — there is no TX1/TX2 split.
payload.signaturestringYesBase64-encoded signature.

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
successbooleanYes—Whether settlement succeeded.
txDigeststringNo—On-chain transaction digest/hash.
receiptIdstringNo—On-chain receipt object ID.
finalityMsnumberNoFinite number.Time to finality in milliseconds.
actualAmountstringNo—Actual amount settled in base units (upto scheme).
depositIdstringNo—UptoDeposit object ID (upto scheme).
streamIdstringNo—Stream object ID (stream scheme).
escrowIdstringNo—Escrow object ID (escrow scheme).
balanceIdstringNo—PrepaidBalance object ID (prepaid scheme).
errorstringNo—Human-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.

One deliberate exception: an accepts[] entry's extra is x402's bag and open by specification, so unknown keys inside it MUST be preserved. A whitelist there is where the next upstream field (paymentFlow, the EIP-712 name / version) goes missing without erroring.

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 ​

Envelope: x402Version, resource, error, accepts, extensions

resource: url, description, mimeType, serviceName, tags, iconUrl

Each accepts[] entry: scheme, network, asset, amount, payTo, maxTimeoutSeconds, extra

s402 keys lifted out of an entry's extra: facilitatorUrl, protocolFeeBps, protocolFeeAddress, receiptRequired, settlementMode, expiresAt, upto, settlementOverrides, prepaid, stream, escrow, unlock, extensions — any OTHER key in extra is preserved verbatim.

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: packageId, keyServers, threshold, contentDigest
  • prepaid: ratePerCall, maxCalls, minDeposit, withdrawalDelayMs, providerPubkey, disputeWindowMs

10.2 Known Payload Keys ​

Top-level: s402Version, scheme, network, payload

Inner payload keys per scheme:

  • exact, stream, escrow: transaction, signature
  • upto: transaction, signature, maxAmount, settlementCeiling
  • unlock: transaction, signature
  • 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 a profile of x402: the 402 leg is x402 V2's own document, and the payment and receipt legs share x402 V1's header names.

The pin. Every constraint this document attributes to x402 is read from x402-foundation/x402 at commit 2cc7e9a6 (@x402/core 2.25.0), typescript/packages/core/src/schemas/index.ts — ResourceInfoSchema, NetworkSchemaV2, PaymentRequirementsV2Schema, PaymentRequiredV2Schema. "The pinned @x402/core decoder" elsewhere in this document means that commit. An s402 implementation is free to be stricter than upstream and MUST NOT be looser on emission.

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 ​

Both are x402 V2 envelopes carrying x402Version: 2. extensions.s402 is what marks the s402 profile, and its absence is not a reason to refuse a 402.

11.3 Retired shapes (intake only) ​

Two flat 402 shapes are no longer emitted by anything and MUST still be readable on intake: x402 V1 (x402Version: 1 with the requirement fields at the top level), and s402 wire v1 (s402Version: "1" with accepts as an array of scheme NAMES). A v1 accepts list expands to one accepts[] entry per scheme, each carrying the document's shared network, asset, amount, payTo and per-requirement fields, with exact hoisted to the front.

11.4 Conversion ​

Implementations MAY provide bidirectional conversion between x402 and s402 formats:

  • x402 V2 → s402: no conversion. That envelope is s402's own document; lift the s402 keys out of each entry's extra and read extensions.s402.
  • x402 V1 → s402: wrap the flat requirement as a single accepts[] entry under a V2 envelope. Use amount (V2) or maxAmountRequired (V1) for the amount field.
  • s402 → x402: every s402 scheme is expressible, one accepts[] entry each. An x402 client without a handler for prepaid or unlock skips that entry — which is what accepts[] is for.

Conversion MUST validate the facilitatorUrl field using the same protocol-only check (§4.2.1) 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.

A 402 carrying no extensions.s402 states no expiresAt, because the field is s402's. On decode, implementations SHOULD derive one for each offer naming an s402 scheme, as now + maxTimeoutSeconds × 1000, and MUST NOT overwrite an expiry the peer stated. Without this, an expiration guard reading an undefined expiresAt skips, and every stale-payment defence is silently absent on exactly the traffic that arrived from outside.

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 every machine-readable conformance test vector shipped in the s402 npm package (196 vectors across 14 files as of v0.9.0)

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.