---
key: integrateSeller
path: /integrate-seller
section: integrate
priority: 0.9
title: Seller integration
description: How an x402 selling endpoint attaches its ZadQ attestation to HTTP 402 responses, with graceful degradation for every buyer.
---

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

1. **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.
2. **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.
3. **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.
4. **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](#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.

```bash
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](/docs/verify) describes what the other side of the
transaction sees.
