Overview
An application that wants to treat verified agents differently, paying out a reward, unlocking a discount, granting access, needs an answer to one question it cannot get by just asking the agent: is this agent backed by a real, unique human? An agent can say anything about itself in conversation, including a DID it doesn’t actually control. This page covers the protocol that answers the question properly: the agent’s DID is never taken on trust, and your application ends up holding proof, not an assertion. This is a self-contained implementation guide. By the end, you’ll have a working, testable integration, and know what to bring to the Billions integration team when you’re ready to go live.Prerequisites
- Node.js 20.11.0 or later.
npm install @billionsnetwork/x402-human-proof-server @billionsnetwork/x402-human-proof-client- An HTTP endpoint your application can expose (the examples below use Express, but the protocol is framework-agnostic).
- On the agent side: the ability to sign an arbitrary message with the wallet key behind the agent’s DID.
Signature support today covers EVM wallets (
eip191, standard EOA signing, used throughout this page) and Solana wallets (ed25519). Smart-contract wallets (eip1271) are not supported yet.A Worked Scenario
Take an application,rewards.example.com, running a rewards program for verified agents: agents complete some task, and human-backed agents get paid a bonus that unverified ones don’t.
The naive approach has a hole: ask the agent for its DID, look that DID up in the Attestation Registry, and pay out if an attestation exists. Nothing stops an agent from reporting a DID it found by browsing the Attestation Explorer, one that genuinely belongs to someone else and genuinely has a verified-human attestation. The lookup would pass. The reward would go to the wrong agent.
The fix is to never ask the agent for its DID at all. The application publishes instructions; the agent follows them and signs a challenge; the application computes the agent’s real DID itself, from that signature. There’s nothing left for the agent to self-report, so there’s nothing left for it to lie about.
How Verification Works
The protocol runs over a single HTTP round trip, using thehuman-proof extension already shipped in @billionsnetwork/x402-human-proof-client and @billionsnetwork/x402-human-proof-server. The three steps below build one continuous example against rewards.example.com from the scenario above.
1
Your application publishes instructions
An endpoint any agent can call, no DID or prior registration required, returning a small JSON challenge: a domain, a resource URI, a nonce, an issue time, and which attestation schema you require. This is the same pattern x402 servers use to tell agents how to pay, just applied to identity instead: the endpoint tells the agent what to sign and where to send it.
2
The agent signs it
The agent fetches the instructions, signs the challenge with its wallet key, and sends the result back as a
HUMAN-PROOF header. Nowhere in this exchange does the agent state its own DID.3
Your application verifies it
Your backend recovers the signing address from the signature, derives the DID from that address, and looks the DID up against the Proof of Uniqueness registry. The response tells you whether it’s allowed, the agent’s real DID, and (if verified) the human ID behind it.
result.did is still exactly right, computed server-side from the signature alone.
Why This Defeats a Malicious Agent
Go back to the rewards scenario. An attacker running their own agent finds a legitimate DID on the Attestation Explorer, one with a real, verified-human attestation, and has their agent claim to be that DID when talking to your rewards application. Your application never reads that claim. It only reads a signature. When the attacker’s agent produces theHUMAN-PROOF header, it can only sign with the private key it actually holds, its own. Your backend recovers the address from that signature and derives the DID from it, which is the attacker’s own DID, not the one they claimed. If the attacker’s real DID has no PoU attestation, verifyHumanProofRequest returns not_registered, and the reward isn’t paid.
The only way to pass this check for a given DID is to hold that DID’s private key. Everything else, chat messages, a claimed identity, a pasted string, carries no weight.
APIs
Two functions are the sanctioned way to check an agent’s verified status. Both wrap the same Attestations API that powers the Attestation Explorer itself, which is otherwise internal.verifyHumanProofRequest(verifier, options)
The full protocol described above: verifies the signature, derives the DID, and checks it against the PoU registry in one call.
Always pass
attestationsApiBaseUrl and nullifierApiBaseUrl explicitly, as shown above, so createPoUVerifier() checks against production.result.reason when result.allowed is false.
A production integration should treat all of these as “not verified, don’t pay out.” Branch on the specific reason only if you want to show the agent a more useful error than a flat rejection.
checkAttestation(did, schemaId)
A plain boolean check against a DID you already trust, for instance one you got back from verifyHumanProofRequest a moment ago, or one obtained through some other verified channel.
Always pass
attestationsApiBaseUrl explicitly, as shown above, so checkAttestation checks against production.Querying the Attestations API Directly
Not on Node.js, or want the raw data instead of a wrapped SDK call? Both functions above are thin wrappers over the same Attestations API endpoints, and you can call them directly. The same scope applies: this is the verification-relevant slice of the API, not its full surface, and it’s rate-limited the same way. Check whether a DID holds an attestation: the same checkcheckAttestation makes.
<schemaID> to 0xca354bee6dc5eded165461d15ccb13aceb6f77ebbb1fd3fe45aca686097f2911 to check for agent authenticity, the same value checkAttestation uses by default as DEFAULT_AGENT_OWNERSHIP_SCHEMA. A non-empty data array means the attestation exists.
Look up every attestation for a DID: the same lookup the Attestation Explorer runs, in either direction. Pass an agent’s DID to find its human, or a human’s DID to find every agent they’ve verified.
These are the production endpoints. Pass
attestationsApiBaseUrl (and nullifierApiBaseUrl, for createPoUVerifier) explicitly, as shown in the snippets above, so the SDK functions check against them too.Ready to Integrate
At this point you have enough to build and test the full flow end to end, including the negative path: point it at an unattested wallet and confirm you correctly getnot_registered back, before you ever need a real verified agent to test against.
When you’re ready to move from testing to production, reach out to the Billions integration team. Have ready: which schema(s) you need beyond the default AgentOwnership check (if any), your expected request volume (the default rate limit may not be enough), and the resource URL(s) your application will verify against.
Learn More
Attestation Explorer
Look up any DID in the public Attestation Registry manually.
How Pairing Works
See the full linking flow that produces these on-chain attestations.