Protocol specification 1.0.0
Six primitives, one economic interaction.
Lifecycle
Discovery → Quote → Session → Intent → Settlement → Receipt
Four stages happen before money moves and are pure signed data. Settlement is the single on-chain step, executed by an x402 facilitator. The receipt is issued after delivery, so a settled payment and a fulfilled service stay distinct facts.
Discovery
A provider publishes what it sells, the assets it accepts, the facilitators it settles through and the key its quotes and receipts are signed with. The registry indexes the document; it is never required to buy.
On the wire
GET /.well-known/epp402.json
| Field | Type | Required | Meaning |
|---|---|---|---|
protocol"epp-402" | "epp-402" | Yes | — |
version"1.0.0" | "1.0.0" | Yes | — |
provider{ id, name, address, payTo, url, description, contact, verification } | { id, name, address, payTo, url, description, contact, verification } | Yes | Who the provider is and which key speaks for it. |
networkstring | string | Yes | CAIP-2 identifier of an EVM network (eip155:<chainId>). |
networksstring[] · optional | string[] | No | — |
assetsobject[] | object[] | Yes | — |
x402{ version, schemes, facilitators } | { version, schemes, facilitators } | Yes | — |
servicesobject[] | object[] | Yes | — |
receipts{ signer, verifyEndpoint } · optional | { signer, verifyEndpoint } | No | — |
updatedAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
signaturestring · optional | string | No | 65-byte secp256k1 signature (r, s, v), 0x-prefixed. |
Served at /.well-known/epp402.json. Optionally attested with an EIP-712 DiscoveryAttestation over the document's canonical hash.
Quote
Deterministic, time-bound pricing signed by the provider's key. Once the agent accepts it, the amount cannot move: the 402 must price the resource at exactly the quoted amount.
On the wire
POST /v1/quotes → signed Quote
| Field | Type | Required | Meaning |
|---|---|---|---|
protocol"epp-402" | "epp-402" | Yes | — |
version"1.0.0" | "1.0.0" | Yes | — |
quoteIdstring | string | Yes | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
provider{ id, address } | { id, address } | Yes | — |
serviceIdstring | string | Yes | Service identifier, unique within the provider. |
paramsobject | object | Yes | — |
paramsHashstring | string | Yes | 32-byte 0x-prefixed hex value. |
quantityinteger · optional | integer | No | — |
price{ amount, asset, decimals, symbol, network } | { amount, asset, decimals, symbol, network } | Yes | — |
payTostring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
buyerstring · optional | string | No | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
sessionIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
issuedAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
expiresAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
noncestring | string | Yes | 32-byte 0x-prefixed hex value. |
conditions{ maxExecutionSeconds, deliverable, refundable, notes } · optional | { maxExecutionSeconds, deliverable, refundable, notes } | No | — |
x402{ scheme, network, asset, amount, payTo, maxTimeoutSeconds, extra } | { scheme, network, asset, amount, payTo, maxTimeoutSeconds, extra } | Yes | — |
signaturestring | string | Yes | 65-byte secp256k1 signature (r, s, v), 0x-prefixed. |
Signed by the provider's key (EIP-712 Quote). The x402 block is what the 402 must advertise, field for field.
Commerce session
The coordination object for one workflow, which may span several providers. It holds the spending limit, every quote, every authorization, what settled and what was delivered. Partial execution is a state, not an error.
On the wire
ses_… · created → quoted → authorized
| Field | Type | Required | Meaning |
|---|---|---|---|
protocol"epp-402" | "epp-402" | Yes | — |
version"1.0.0" | "1.0.0" | Yes | — |
sessionIdstring | string | Yes | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
buyer{ address, agent } | { address, agent } | Yes | — |
status"created" | "quoted" | "authorized" | "executing" | "settled" | "completed" | "partially_settled" | "expired" | "failed" | "cancelled" | "created" | "quoted" | "authorized" | "executing" | "settled" | "completed" | "partially_settled" | "expired" | "failed" | "cancelled" | Yes | — |
networkstring | string | Yes | CAIP-2 identifier of an EVM network (eip155:<chainId>). |
assetstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
spendingLimitstring | string | Yes | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
authorizedstring | string | Yes | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
spentstring | string | Yes | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
providersstring[] | string[] | Yes | — |
itemsobject[] | object[] | Yes | — |
createdAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
updatedAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
expiresAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
historyobject[] | object[] | Yes | — |
metadataobject · optional | object | No | — |
Off-chain state kept by the buyer's SDK or a hosted session service, published to the registry with the buyer's attestation.
Payment intent
The agent's authorization of one payment, under its spending policy: payer, recipient, exact amount, window and the nonce the token will burn, so an intent can settle at most once.
On the wire
EIP-712 by the payer · bound to the EIP-3009 nonce
| Field | Type | Required | Meaning |
|---|---|---|---|
protocol"epp-402" | "epp-402" | Yes | — |
version"1.0.0" | "1.0.0" | Yes | — |
intentIdstring | string | Yes | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
sessionIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
quoteIdstring | string | Yes | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
payerstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
recipientstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
assetstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
networkstring | string | Yes | CAIP-2 identifier of an EVM network (eip155:<chainId>). |
amountstring | string | Yes | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
ceilingstring · optional | string | No | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
validAfterinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
validBeforeinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
noncestring | string | Yes | 32-byte 0x-prefixed hex value. |
scheme"exact" | "exact" | Yes | — |
status"created" | "signed" | "submitted" | "settled" | "failed" | "expired" | "cancelled" | "created" | "signed" | "submitted" | "settled" | "failed" | "expired" | "cancelled" | Yes | — |
createdAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
updatedAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
signaturestring · optional | string | No | 65-byte secp256k1 signature (r, s, v), 0x-prefixed. |
execution{ txHash, settlementId, submittedAt, settledAt, error } · optional | { txHash, settlementId, submittedAt, settledAt, error } | No | — |
Signed by the payer (EIP-712 PaymentIntent). Its nonce is the EIP-3009 nonce the token burns on settlement, so an intent settles at most once.
Settlement
The facilitator verifies the signed authorization against the requirements, broadcasts it on Elysium and waits for the receipt. The payer never pays gas and the facilitator never holds funds.
On the wire
PAYMENT-SIGNATURE → facilitator → Elysium
| Field | Type | Required | Meaning |
|---|---|---|---|
protocol"epp-402" | "epp-402" | Yes | — |
version"1.0.0" | "1.0.0" | Yes | — |
settlementIdstring | string | Yes | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
networkstring | string | Yes | CAIP-2 identifier of an EVM network (eip155:<chainId>). |
scheme"exact" | "exact" | Yes | — |
assetstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
amountstring | string | Yes | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
payerstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
payTostring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
noncestring · optional | string | No | 32-byte 0x-prefixed hex value. |
txHashstring | string | Yes | 32-byte 0x-prefixed hex value. |
blockNumberinteger · optional | integer | No | — |
status"pending" | "confirmed" | "failed" | "pending" | "confirmed" | "failed" | Yes | — |
confirmationsinteger · optional | integer | No | — |
facilitatorstring · optional | string | No | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
relaystring · optional | string | No | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
quoteIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
intentIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
sessionIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
receiptIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
resourcestring · optional | string | No | — |
submittedAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
confirmedAtinteger · optional | integer | No | Integer Unix timestamp in seconds (UTC). |
error{ code, message } · optional | { code, message } | No | — |
Recorded by the facilitator. Only `confirmed` is proof of payment, and payment is not proof of delivery.
Receipt
The provider's signed statement that this payment bought this service and whether it was delivered. Anyone can verify it: recover the signature, read the transaction, compare the transfer.
On the wire
EPP-RECEIPT · EIP-712 by the provider
| Field | Type | Required | Meaning |
|---|---|---|---|
protocol"epp-402" | "epp-402" | Yes | — |
version"1.0.0" | "1.0.0" | Yes | — |
receiptIdstring | string | Yes | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
sessionIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
intentIdstring · optional | string | No | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
quoteIdstring | string | Yes | EPP-402 object identifier: a short type prefix, an underscore and 26 Crockford base32 characters. |
payerstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
provider{ id, address } | { id, address } | Yes | — |
service{ id, resource, resultHash } | { id, resource, resultHash } | Yes | — |
amountstring | string | Yes | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
assetstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
networkstring | string | Yes | CAIP-2 identifier of an EVM network (eip155:<chainId>). |
txHashstring | string | Yes | 32-byte 0x-prefixed hex value. |
settledAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
fulfillment{ status, deliveredAt, detail } | { status, deliveredAt, detail } | Yes | — |
issuedAtinteger | integer | Yes | Integer Unix timestamp in seconds (UTC). |
signaturestring | string | Yes | 65-byte secp256k1 signature (r, s, v), 0x-prefixed. |
Signed by the provider (EIP-712 Receipt). The fulfillment status is inside the signed struct.
Partial execution is a state, not an exception.
Every allowed transition, as the implementation enforces it. Terminal states accept nothing. A multi-provider session is never represented as an atomic transaction; the manager tracks what settled, what failed and what still needs compensation.
| From | Can move to | Terminal |
|---|---|---|
| Created | quotedcancelledexpired | No |
| Quoted | quotedauthorizedcancelledexpired | No |
| Authorized | authorizedquotedexecutingcancelledexpired | No |
| Executing | executingsettledpartially_settledfailed | No |
| Partially settled | partially_settledexecutingsettledcompletedfailed | No |
| Settled | partially_settledcompletedfailed | No |
| Completed | — | Yes |
| Expired | — | Yes |
| Failed | — | Yes |
| Cancelled | — | Yes |
Four signed structs, one domain, bound to the chain.
Domain EPP-402, version 1, with the chain id of the settlement network, so a quote or receipt signed for one network cannot be replayed as another's. Identifiers travel as keccak256 of their string form; a zero hash means none.
Quote
quoteIdbytes32provideraddressserviceIdbytes32paramsHashbytes32amountuint256assetaddresschainIduint256payToaddressbuyeraddresssessionIdbytes32issuedAtuint64expiresAtuint64noncebytes32
PaymentIntent
intentIdbytes32sessionIdbytes32quoteIdbytes32payeraddressrecipientaddressassetaddresschainIduint256amountuint256validAfteruint64validBeforeuint64noncebytes32
Receipt
receiptIdbytes32sessionIdbytes32intentIdbytes32quoteIdbytes32payeraddressprovideraddressassetaddresschainIduint256amountuint256txHashbytes32settledAtuint64serviceIdbytes32resultHashbytes32fulfillmentuint8issuedAtuint64
DiscoveryAttestation
providerIdbytes32provideraddressdocumentHashbytes32updatedAtuint64
SessionAttestation
sessionIdbytes32buyeraddressdocumentHashbytes32updatedAtuint64
Commerce references ride in the one place x402 reserves for them.
A quote's x402 block is exactly what the 402 must advertise. The quote, session and intent identifiers travel under extensions.epp402 and are echoed back by the client, so the facilitator and the explorer can tie a settlement to a session without any change to the x402 wire format.
| Field | Type | Required | Meaning |
|---|---|---|---|
version"1.0.0" | "1.0.0" | Yes | — |
namestring · optional | string | No | — |
networkstring | string | Yes | CAIP-2 identifier of an EVM network (eip155:<chainId>). |
assetstring | string | Yes | EVM address, 0x-prefixed, 40 hex characters. Compare case-insensitively. |
maxPerTransactionstring · optional | string | No | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
maxPerSessionstring · optional | string | No | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
maxPerDaystring · optional | string | No | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
allowedProvidersstring | string[] · optional | string | string[] | No | — |
allowedAssetsobject[] · optional | object[] | No | — |
authorizationTtlSecondsinteger · optional | integer | No | — |
approvalThresholdstring · optional | string | No | Unsigned integer amount in the asset's minor units, as a decimal string (BigInt-safe). |
Evaluated by the SDK before it signs. An off-chain policy restricts what the SDK will sign; wallet-level limits are recommended wherever the wallet supports them.
{
"x402Version": 2,
"resource": {
"url": "https://inference.example.com/v1/inference",
"description": "Standard inference",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:99801",
"asset": "0x…EPPUSD",
"amount": "10000",
"payTo": "0xb1297Fea0b54A79BdBCfb8b057e39B226Cc2ebFc",
"maxTimeoutSeconds": 300,
"extra": {
"name": "EPP-402 Test USD",
"version": "1"
}
}
],
"extensions": {
"epp402": {
"info": {
"version": "1.0.0",
"providerId": "inference.example.com",
"quoteId": "qt_01k9r3w6zj8e2n0h4yv7c5b9ma"
}
}
}
}Semantic versions, frozen only after interoperability tests.
Every object carries its schema version. The core wire format is not frozen before interoperability tests are complete; once finalised, backward compatibility is maintained under semantic versioning.
| Specification | Version |
|---|---|
| EPP-402 Core | 1.0.0 |
| Discovery schema | 1.0.0 |
| Quote schema | 1.0.0 |
| Session schema | 1.0.0 |
| Intent schema | 1.0.0 |
| Settlement schema | 1.0.0 |
| Receipt schema | 1.0.0 |
| Elysium adapter | 1.0.0 |
| x402 compatibility | v2 |