Verification patterns
On this page
Every pattern ends in the same check: the agent is verified when hasHumanProof && isProofFresh on the registry.
| Pattern | The agent | The verifier |
|---|---|---|
| Agent-to-service | Signs HTTP requests | Recovers the signer and checks the registry (SelfAgentVerifier) |
| Agent-to-agent | Signs requests and responses | Both sides verify each other, and can check for a shared human |
| Agent-to-chain | Calls a contract as msg.sender | The contract reads the registry |
| Lookup | Shares its agent ID or address | Reads the registry, the REST API, A2A or MCP, with no signature |
Agent-to-service
Agent Service
│── POST /api (signed) ─────────────▶│
│ x-self-agent-address │── recover signer from the signature
│ x-self-agent-signature │── agentKey = zeroPad(signer, 32)
│ x-self-agent-timestamp │── isVerifiedAgent + isProofFresh
│ │── provider, sybil, credential policy
│◀── 200 OK ──────────────────────────│
The agent calls agent.fetch(). The service uses verifier.auth() (Express) or verifier.verify(). Options are in SDK integration, and the setup walkthrough is in Verifying agents.
Agent-to-agent
Both agents sign their messages with SelfAgent and verify the other’s with SelfAgentVerifier. To find out whether two agents are backed by the same human, without learning who that human is:
- compare
req.agent.nullifier(orgetInfo().nullifier) of the two agents, or - call
sameHuman(agentIdA, agentIdB)on the registry.
Agent-to-chain
The agent sends a transaction from its own EVM key, and the contract derives the agent key from msg.sender:
bytes32 agentKey = bytes32(uint256(uint160(msg.sender)));
require(
registry.isVerifiedAgent(agentKey) && registry.isProofFresh(registry.getAgentId(agentKey)),
"Agent not human-verified"
);
isVerifiedAgent alone does not check expiry, so always pair it with isProofFresh. See Gating smart contracts.
Lookup without a signature
When you only need to know whether an agent ID or key is verified (dashboards, allowlists, A2A peers):
- SDK:
verifyAgent(agentKey, { network }) - REST:
GET /api/agent/verify/{chainId}/{agentId}or/api/verify-status/{chainId}/{agentId}(Agent queries) - A2A: the
verifyorfreshnessintent - MCP:
self_verify_agent
A lookup proves the agent exists and is human-backed, not that the caller controls it. For access control, verify a signature.
Sybil resistance
Each human has one nullifier, and the registry caps active agents per human (maxAgentsPerHuman, default 1). Services set their own limit on top:
| Policy | Builder |
|---|---|
| One agent per human | .sybilLimit(1) (default) |
| Up to N agents per human | .sybilLimit(n) |
| No limit (detection only) | .sybilLimit(0), then compare nullifiers or call sameHuman() |
Credential policies
Require ZK-attested credentials disclosed at registration:
const verifier = SelfAgentVerifier.create()
.requireAge(18) // the human proved 18+
.requireOFAC() // passed OFAC screening
.requireNationality('USA', 'GBR') // ISO 3166-1 alpha-3
.build();
An agent passes a credential check only if the human disclosed that credential when registering, so pick the matching verification config at registration.
Thanks. What went wrong?
Thanks for your feedback!