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.

ProofWhat it provesChecked by
Web Bot Auth (RFC 9421, tag="web-bot-auth")The operator holds the key in its published key directoryThe API, against <Signature-Agent>/.well-known/http-message-signatures-directory
Published IP rangesThe request came from addresses the operator publishesThe API, against lists refreshed daily
ERC-8128 request signatureAn Ethereum wallet signed the requestThe API, on the requests the page's Double Agent script sends
A declared User-Agent (ChatGPT-User, Claude-User)NothingRead 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.verified and da_verified turn true only after check(), 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"
HintSet whenSignal
wba=1The 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=1Cloudflare Bot Management marks a verified botedge.cf_verified_bot
cfs=<1-99>Cloudflare's bot score; below 30 counts as bot evidenceedge.cf_score
dc=1The 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

  1. Make an Ed25519 key pair.
  2. 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.
  3. On every request, send Signature-Agent: "https://<your host>", plus Signature-Input and Signature per RFC 9421. The signature must cover @authority and signature-agent, carry created, expires (at most 24 hours apart), keyid (the key's JWK thumbprint, or its kid), alg="ed25519" if you set alg, and tag="web-bot-auth".
  4. 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.
  5. 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.

ResultShown as
The signature checks outLevel verified, and Signed telemetry on the session
The signer is also the registry owner or agent wallet of the declared tokenERC-8004 wallet as well
It does not check out, or the chain could not be readerc8128.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.