Seller integration
This guide describes how a seller running an x402 endpoint attaches its attestation once it is issued. The integration is additive: it extends responses the endpoint already sends, and buyers that ignore the extension keep working unchanged.
Before you integrate: your account
The attestation comes out of the dashboard at https://zadq.app, under an account. Next steps (https://zadq.app/#/account/next/seller) ticks each step from the account's state and carries the one to do now.
- Create your account at
https://zadq.app/#/sign-up?for=seller(or
?for=organisation). The responsible person gives their name and work e-mail, says who we contract with, accepts the Terms and creates a passkey. From then on, sign in with your passkey.
- Bind the wallet and register your endpoint. Under the account, the
wallet that controls your payTo address signs a challenge, which proves the address is yours, and you register the resource URL. The wallet is bound to the account as its payTo binding; it is never a login.
- Verify. The responsible person's identity verification (KYC), on
the provider's page, inside the issuer's boundary. For an organisation, the company's details are that person's declaration; business verification (KYB) is not available on this deployment yet. No document is asked in the dashboard.
- Acknowledge the terms with the same wallet.
- Choose your tier on the hosted checkout, after adding your invoicing
details (legal name, registered address, and the EU VAT number or national tax number). Amounts live on the billing provider's checkout page.
- Issue the attestation. It binds the accountable party to the endpoint
identifiers: chain, payTo and resource URL.
- Take the credential offer into your holder (v1, see
- Read your first verdict, the same one any buyer reads for free.
Two proofs, never interchangeable: the passkey is the responsible person's session for everything in the account; the wallet proves control of the payTo address. Every write that touches that payTo (the registration, the terms acknowledgement, a rotation to a new wallet) is signed again by the bound wallet inside the passkey session. Everything else, including your notification receivers, runs on the passkey session alone.
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 is 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.
The freshness band of the identification is a different thing: how recently the issuer checked the evidence behind your verification (F4 under 30 days, F3 under 90, F2 under 180, F1 under 365 — a band, never a date). The issuer computes it and publishes it beside the grade; it is read from the verification session or the attestation, and your dashboard shows it. A presentation of your credential (v1) proves the grade threshold only, never recency; "human-backed at G" means grade 2 or above and freshness F1 or better, and the issuer enforces it.
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 consumer wallet and none is planned, 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:
- Get the offer on your endpoint's Credential page 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 verify again; the v0 rail keeps working meanwhile.
- Redeem it at once with the holder module of the SDK (
holder,
import { SellerHolder } from '@zadq/x402/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 verify again and receive a fresh credential.
- Expose one presentation endpoint and register it in the dashboard when
you record the credential as 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 within 245 s of the revocation being recorded 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 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; the issuer folds it into a coming root, and within the published bound — 245 s from the recording — the credential presents nowhere. 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 Record the revocation on your endpoint's Credential page (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. Verify again 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.