Skip to content
OpenSouk

Make your 402 self-describing

Declare the attribution contract in the response body

A referral-mode 402 carries three extension fields. They are not what attributes the payment — the facilitator reads attribution off the buyer's payload, and the protocol's own tooling fills those in from the token it already holds. What the fields do is declare what a payment to you must carry, so an agent with no prior knowledge of this protocol can read your response and comply — as long as it signs the ERC-3009 authorisation itself rather than delegating to a stock x402 SDK, whose EIP-712 type table is pinned to TransferWithAuthorization. This rail needs ReceiveWithAuthorization; see Rails. They are also what our readiness probe asserts. The token itself, and what it binds, is on The attribution token.

What you will build

One extensions object on the 402 you already return in referral mode:

{
  "extensions": {
    "attributionToken": "eyJ2Ijo0LCJy…",
    "buyerAgentId": "7",
    "setup_url": "https://api.opensouk.ai/.well-known/referrer-agent"
  }
}

It sits at the top level of the 402 body, beside x402Version and accepts — not inside accepts[0]. The full response shape is in Accept agent payments over x402.

The three fields

FieldValueRequired for a payment to settle
attributionTokenThe X-Referrer-Token request header, echoed verbatimThe buyer must send it on the payload; your echo is a declaration, not the source
buyerAgentIdThe X-Referrer-Buyer-Agent-Id request header, as a stringNo. Absent means an agent-less buyer
setup_urlThe protocol API's /.well-known/referrer-agent URLNo

Echo, do not re-encode. attributionToken is unpadded base64url and the facilitator decodes it strictly. Adding padding, switching to standard base64 or URL-escaping it produces a value a buyer copying your declaration cannot spend.

buyerAgentId is a string, and only present when the header was. A number is rejected by the facilitator when a buyer copies it into the payload, and an absent header echoed as "" is read as an agent-less buy. Agent id 0 is a real identity, so never treat a 0 as "not sent".

setup_url points at the API, not at the facilitator. The manifest is served by the protocol's API host at api.opensouk.ai; the facilitator serves /verify, /settle, /supported, /attribution/public-key and the charge route, and nothing at /.well-known. Pointing it at the facilitator's base URL yields a 404 for the one reader it exists for — a cold agent that has just failed to pay and is looking for how to onboard.

Prompt mode

Show the prompt
Add the referral extensions object to this service's referral-mode 402 on Base mainnet.

At the TOP LEVEL of the 402 body (a sibling of x402Version and accepts, not inside
accepts[0]), add an "extensions" object with:
  - attributionToken: the X-Referrer-Token request header value, echoed byte for byte. Do
    not re-encode, pad, or URL-escape it.
  - buyerAgentId: the X-Referrer-Buyer-Agent-Id request header value as a JSON STRING,
    included only when that header was actually sent. Never coerce it to a number, and
    never treat the string "0" as absent.
  - setup_url: the protocol API's https://<api host>/.well-known/referrer-agent URL. This
    is the API host, not the facilitator's base URL.

Leave the rest of the 402 unchanged.

Manual mode

Read the two request headers

const attributionToken = req.header('X-Referrer-Token')
const buyerAgentId = req.header('X-Referrer-Buyer-Agent-Id')

X-Referrer-Token is the value you echo, not a switch to read: a referral-dedicated endpoint answers this way for every request. (An endpoint serving both modes from one URL does use the header's presence to choose, which is one of the reasons the dedicated one is simpler.) X-Referrer-Buyer-Agent-Id is informational, and its absence is meaningful rather than an error.

Build the object with the optional field omitted

const extensions = {
  attributionToken,
  ...(buyerAgentId ? { buyerAgentId } : {}),
  setup_url: SETUP_URL,
}

Both an omitted key and an empty string settle; the omission is the honest declaration that this buyer sent no identity.

Configure setup_url once

Store it as configuration, alongside your facilitator URL. Both are deployment settings.

export SETUP_URL=https://api.opensouk.ai/.well-known/referrer-agent
curl -s "$SETUP_URL" | head -c 200

The manifest is one JSON document, with a .txt prose companion at the same path. It carries the per-chain attestation public key an agent verifies a token against, the contract addresses per chain, and a native-integration recipe.

One line of that recipe is stale. It tells an agent to POST the payment in an X-PAYMENT header, the x402 v1 name. The protocol's buyer tooling sends PAYMENT-SIGNATURE, and a merchant that reads only X-PAYMENT never sees a payment from a protocol buyer.

Check your own response

curl -s -o /dev/null -w '%{http_code}\n' \
  -H 'X-Referrer-Token: readiness-probe' https://rpc.example.com/v1/mainnet
 
curl -s -H 'X-Referrer-Token: readiness-probe' \
  https://rpc.example.com/v1/mainnet | jq '{x402Version, extensions, payTo: .accepts[0].payTo}'

readiness-probe is the literal value our own probe sends, and it is not a real token — which is the point. It exercises the mode switch and the response shape without a purchase.

Verify

The readiness probe's echoes_attribution check is what asserts this object exists. It tests for the presence of the attributionToken key inside extensions and nothing else: an empty string passes, and a wrong value passes. So a passing check means "the field is declared", not "the echo is correct" — the echo is verified by the facilitator at payment time, and by reading your own response as in the last step.

Errors and retries

What you seeCause
echoes_attribution fails with pass: falseNo extensions object, the key spelled differently, or the object nested inside accepts[0]
A buyer reports the payment was refused with missing extensions.attributionTokenThe buyer's payload carried no token. Your 402 cannot cause this and cannot fix it; the rejection itself carries setup_url
A buyer reports invalid extensions.buyerAgentIdYou echoed a number, or a re-formatted string. Echo the header text
setup_url returns 404It points at the facilitator's base URL instead of the API host

Next steps