File DA-018 View as Markdown
Verified agents
This page answers "is this agent really ChatGPT?". It explains what proof Double Agent checks before it calls an agent verified, where you see the result, and, if you build an agent, how to sign your requests so sites count you as verified. It is the first of the pages for agent builders in this group.
Declared and verified
Anyone can put ChatGPT-User in a User-Agent header. That names a family but proves nothing, so Double Agent calls it declared. An agent is verified only when it proves who runs it, and only the server can check that proof.
| Proof | What it proves | Checked by |
|---|---|---|
Web Bot Auth (RFC 9421, tag="web-bot-auth") | The operator holds the key in its published key directory | The API, against <Signature-Agent>/.well-known/http-message-signatures-directory |
| Published IP ranges | The request came from addresses the operator publishes | The API, against lists refreshed daily |
| ERC-8128 request signature | An Ethereum wallet signed the request | The API, on the requests the page's Double Agent script sends |
A declared User-Agent (ChatGPT-User, Claude-User) | Nothing | Read as a claim only |
Reverse DNS is not checked yet.
A request that claims a crawler which publishes its addresses, but comes from outside them, is flagged (ip.outside_published_list), not verified.
Where you see it
- In the browser verdict:
agent.verifiedandda_verifiedturn true only aftercheck(),getToken()or
protect() returns a verified agent. Only operators in the open catalog count here: a valid signature from a key directory the catalog doesn't know proves who holds the key, not who they are.
- In the signed token: your server reads the same result from the token. For access decisions, trust the token,
not the browser. See @doubleagent-so/node.
- In Callers: the caller's level is
verified, verified by Double Agent. Here a valid signature from any key
directory counts. See Callers.
Verification never blocks or allows anything by itself. It is a label you can act on.
Edge hints
Your edge can add a hint to each HTML page, which the browser script reads as extra evidence. The edge adapter sets it for you. A custom edge can set the same header:
Server-Timing: doubleagent;desc="v=1&wba=1&ua=claude&cfv=1&cfs=12&dc=1"
| Hint | Set when | Signal |
|---|---|---|
wba=1 | The request carries Web Bot Auth headers (not checked at the edge) | edge.web_bot_auth_present |
ua=<family> | The User-Agent names an agent family (openai, claude, perplexity, google, …) | ua.declared_agent |
cfv=1 | Cloudflare Bot Management marks a verified bot | edge.cf_verified_bot |
cfs=<1-99> | Cloudflare's bot score; below 30 counts as bot evidence | edge.cf_score |
dc=1 | The request came from a hosting network (AWS, Google Cloud, Azure and others) | net.datacenter_asn |
Anyone who controls the browser can forge this header, so hints can only raise suspicion, and their weight is capped. They never make an agent verified.
For agent builders: get counted as verified
Sign with Web Bot Auth
- Make an Ed25519 key pair.
- Publish the public key as a JWKS at
https://<your host>/.well-known/http-message-signatures-directory:{ "keys": [{ "kty": "OKP", "crv": "Ed25519", "x": "<base64url>" }] }. Keep it under 64 KiB. - On every request, send
Signature-Agent: "https://<your host>", plusSignature-InputandSignatureper RFC 9421. The signature must cover@authorityandsignature-agent, carrycreated,expires(at most 24 hours apart),keyid(the key's JWK thumbprint, or itskid),alg="ed25519"if you setalg, andtag="web-bot-auth". - Sign every request your agent makes, including the ones the page's Double Agent script sends to
api.doubleagent.so. That is where Double Agent checks the signature for websites. - Keep clocks in sync: 60 seconds of skew is allowed.
Signature-Agent: "https://agent.example"
Signature-Input: sig1=("@authority" "signature-agent");created=1791417600;expires=1791421200;keyid="<thumbprint>";alg="ed25519";tag="web-bot-auth"
Signature: sig1=:<base64 signature>:
With this, Callers shows you as verified. To have the browser verdict say agent.verified too, add your agent and its Signature-Agent origin to the open catalog in agent-detector (see its CONTRIBUTING). Also keep a stable User-Agent product, such as acme.shopping-assistant/1.0, and don't copy another agent's family: a claim the evidence contradicts is recorded as spoofed.
ERC-8004 declarations and ERC-8128 signatures
An agent on the ERC-8004 registry can name itself in the User-Agent with a comment:
acme.shopping-assistant/1.0 (erc8004=eip155:1:0x8004a169fb4a3325136eb29fa0ceb6d2e539a432:22)
The comment is a Double Agent convention. It is a declaration only, so the level stays declared.
To prove it, the agent's Ethereum wallet can sign the requests the page's Double Agent script sends to api.doubleagent.so (collect and check) with ERC-8128 (draft). The signature must cover the URI, method, body, content type, and the User-Agent and Origin when present. It is valid for at most 300 seconds, with 30 seconds of clock skew, and works once. Plain wallets (EOAs) and deployed ERC-1271 smart accounts are supported; delegated and counterfactual accounts are not.
| Result | Shown as |
|---|---|
| The signature checks out | Level verified, and Signed telemetry on the session |
| The signer is also the registry owner or agent wallet of the declared token | ERC-8004 wallet as well |
| It does not check out, or the chain could not be read | erc8128.invalid or erc8128.unavailable beside the level |
These prove that the wallet signed what was sent to Double Agent. They don't prove the request to the website itself, and they never change the verdict's class or what the agent may do. Requests your edge reports keep the declaration only. To try it on a site, the CLI's simulate --sign-requests signs with a key from DOUBLEAGENT_ETHEREUM_PRIVATE_KEY (see CLI).
Next
- Agent Cards: publish and sign your card so the directory lists you.
- Callers: how levels show to the businesses you call.
- Verdict: every field of the verdict, including
agent.verified.