Seller integration
This guide describes how an operator of an x402 selling endpoint attaches its attestation once admitted. The integration is additive: it extends responses the endpoint already sends, and buyers that ignore the extension keep working unchanged.
Prerequisites
- An admitted operator. Admission runs off the transaction path, once: an
authorised issuer reviews the legal entity and the operator deposits the guarantee deposit.
- The signed credential issued on admission, binding the operator and bond
to the endpoint identifiers.
Attaching the attestation
The seller's middleware attaches the attestation to the HTTP 402 response as an additive identityPolicy extension. A response without the extension remains a valid x402 response; a response with it gives the buyer the option to verify before paying.
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402Version": 1,
"accepts": [ ... ],
"extensions": {
"identityPolicy": {
"claim": "seller-endpoint-accountability",
"assuranceAtLeast": "G",
"verifyUrl": "https://verification.example/v0/verifications",
"verdict": { "...": "optional inline copy of the latest signed verdict" }
},
"trustPerimeter": {
"attestation": "welcome",
"attestUrl": "https://agents.example/attest"
}
}
}
identityPolicy is the canonical v0 seller object of the x402 identity profile the MintID identity network publishes: one claim, exactly these fields, nothing else inside it. The inline verdict is a hint for a local check; the authoritative answer is always the free path at verifyUrl.
trustPerimeter is the vendor's own invitation to the trust perimeter, under its own key so the canonical object stays untouched: welcome (any agent pays as usual; attested ones are welcome) or preferred (the vendor prefers attested counterparties). Neither changes the payment flow, and a reader that ignores the key pays exactly as before. Requiring attested agents is not a value of this block; it arrives with v1 as the vendor's own policy, run on the vendor's own verifier.
The companion request header X-PAYMENT-IDENTITY lets either side carry proof in both directions when a transport does not surface response extensions.
The issuer's receipt
On admission the issuer signs a receipt of the attestation: the endpoint identifiers (chain, payTo, resource URL), the assurance grade, the issuance instant, the issuer identifier and the attestation reference, under the issuer's registered signing key. The dashboard serves it to the operator; the seller's middleware may put it on X-PAYMENT-IDENTITY as an attestation-ref/v0 proof, and a receiver may verify the signature locally with the issuer's public key from the substrate's issuer registry.
The receipt proves that the attestation was issued. It says nothing about whether it is active today: standing is read from the free verification path, or from a verifier's own standing read, never from the receipt. A receiver that cannot verify a receipt handles the request exactly as it would without the header.
What the middleware guarantees
- Additive only. No existing field changes meaning; buyers unaware of
the extension are unaffected.
- No round-trips added. The attestation rides on responses the endpoint
already sends; attaching it adds no network calls to the payment flow.
- Stateless. The credential is signed material; the middleware holds no
session and calls no external service to attach it.
Renewal
Attestation freshness is bounded by a renewal cadence promised in the credential itself. The middleware refreshes the signed material on that cadence; a buyer checks freshness against the same promise, so an endpoint that stops renewing degrades to unknown on the buyer side — it does not break.
Holding your credential (v1)
On the v1 rail the accountable party behind the endpoint holds its own credential and proves it, instead of being vouched for by a signed receipt alone. The credential lives in your own infrastructure, in a headless holder library that runs next to the payment key you already protect. There is no wallet, no hosted key and no key export; this service never holds a credential, a key, an offer code or a proof — the dashboard shows state only (offered, held, expired, revoked) and hands the offer out once.
The steps, in order:
- Request the offer from the dashboard once your verification is
approved. The offer is created from that approved verification and nothing else is asked of you; what the credential carries is read by the issuer inside its own boundary. An offer is one-shot and expires exactly with the verification behind it — no grace. Where no offer can be made on a verification, the dashboard asks you to re-verify; the v0 rail keeps working meanwhile.
- Redeem it at once with the holder module of the SDK (
holder), which
drives the holder library as a child process. Keep the custody encoding it produces sealed under a key you control (an environment secret, a cloud KMS data key or an HSM-wrapped key), where the payment key lives. A lost key is recoverable by nobody: you re-verify and receive a fresh credential.
- Expose one presentation endpoint and register it in the dashboard when
you report the credential held. On the published cadence the service posts its verifier's challenge there; the holder refreshes its witness, answers with one bound presentation inside the ten-second window, and the verifier's decision governs the positive claim on your endpoint. A holder that cannot present answers with a reason code, and the verdict falls back to the v0 standing — nothing blocks.
- Revoke your own credential if its key leaks: the holder builds a
self-revocation that names neither you nor the credential; from that moment it refuses to present, and from the next status root on the credential presents nowhere. See Revoking your credential.
During the coexistence window the 402 keeps carrying identityPolicy (v0); the SDK emits the complete v1 object mintid_identity beside it only while the credential is held and you declare a v1 policy — one a paying party can prove to a challenge endpoint you or your facilitator operate. Your own credential is never carried on the 402: it is presented to this service's verifier, and the verdict stays readable on the free path. A middleware without a credential emits v0 only.
Revoking your credential
If the key next to your credential leaks, you revoke the credential yourself — the kill switch. It runs where the key is, in your own middleware, and it names neither you nor the credential: the network's privacy relay carries it, and this service is not involved in the act at all.
What happens, in order. The SDK's revokeCredential (or the command it ships for the middleware host) waits a random moment of your own, takes a fresh witness so the self-revocation is built against the root the chain holds, lets the holder library build it — from that instant the holder refuses to present, also after a restart from custody — and hands it to the network's privacy relay, which checks it, pays for the transaction and submits it. You sign nothing, hold no account and never talk to the chain to submit. The relay drops your address and headers before its code sees the request and keeps no access log. The relay's answer is never the receipt: the SDK then asks a node of the chain for the issuer's recorded revocations and finds yours locally, so the node never learns which one you hold.
What the report says. revoked — the chain holds the record and the issuer has folded it: the credential presents nowhere, including any copy taken before. pending — recorded; it lands at the issuer's next status root, inside the published bound (a root every 30 s, accepted for at most 180 s). unconfirmed — the relay accepted it but no node showed the record within the read window, or no node was configured: check again or resubmit through a relay; both are safe, and the holder refuses to present regardless. The report carries states, reason codes and what the chain publishes anyway — never the revocation value, never the proof — and nothing is logged.
MINTID_HOLDER_BIN=/usr/local/bin/mintid-holder \
CREDENTIAL_CUSTODY_FILE=/var/lib/seller/credential.custody CREDENTIAL_CUSTODY_KEY=<64 hex> \
MINTID_RELAY_URL=<the relay> MINTID_CHAIN_RPC=<a node> \
node --experimental-strip-types src/holder/cli.ts --yes
Then record it. Press Revoke my credential → My middleware revoked it in the dashboard (or run the command with --report). The service records the state — self_revoked — and nothing next to it: no time, no reference, no log line, no notification. It is terminal on that verification.
What buyers see. Nothing changes at the moment you record it: the public verdict keeps its last value until the next re-check, when your endpoint's attestation is retired and the verdict reads revoked from then on. Every integration keeps working meanwhile — a buyer reads the last verdict, then revoked; nothing blocks.
How to hold a credential again. Re-verify from the dashboard. Once the new verification is approved the credential state returns to none, a fresh offer follows and a new attestation can be issued. The new credential carries nothing of the old one. There is no re-offer on the revoked verification.
Next: buyer verification describes what the other side of the transaction sees.