Measuring
Verified agent identity
Every analytics tool in this category identifies a bot by reading its user-agent string. A user-agent is a sentence the client wrote about itself. When a dashboard reports “GPTBot read 4,120 pages”, what it actually knows is that 4,120 requests said they were GPTBot — and nothing in the industry distinguishes the two.
A cryptographic signature is different. Web Bot Auth — an IETF draft led by Cloudflare, built on RFC 9421 HTTP Message Signatures — lets an agent sign its requests. Anthropic, OpenAI, Perplexity and Common Crawl have started doing it. When a request arrives signed, we check the signature against the Ed25519 key its operator publishes and we can name the signer.
Three states, never merged
| Label | What it means |
|---|---|
verified | A signature was checked against the operator's published key. We can tell you which directory vouched for it. |
claimed | The user-agent matches a bot we know, and nothing was signed. This is all a user-agent can ever be worth. |
human | Neither. |
What this deliberately does not claim
An unsigned request is not evidence of anything. Almost nothing signs yet. If we treated unsigned traffic as suspicious we would have replaced one unprovable claim with another, which is the opposite of the point. Verification can only ever promote a label; it never demotes a visitor to a bot.
When a signature arrives from an operator whose key directory we have not fetched yet, the request is
recorded as claimed with the reason key_not_cached
— not as a failure. Fetching a key directory on the ingest path would make a slow signer into our
latency, so the fetch happens in the background and the next request from that operator verifies.
How a signature reaches us
It travels on the request for your page. By the time JavaScript runs the headers are gone, so our browser script can never see one — and a crawler that does not execute JavaScript never reaches the script at all (see bot detection).
Both problems have the same answer: forward what your server served.
POST https://app.vitrus.dev/api/collect/server
{ "site": "SITE_ID", "url": "/pricing", "ip": "203.0.113.9",
"headers": { "user-agent": "...", "signature-agent": "...",
"signature-input": "...", "signature": "..." } }
The endpoint is as public as the tracker — your site id is a write token, not a secret. That does not
weaken verified, which is the label that matters: producing one would
require the operator's private key.
What we implement, exactly
Web Bot Auth is an Internet-Draft, not yet an RFC; RFC 9421 underneath it is a ratified standard. We implement the profile Web Bot Auth uses and refuse everything else rather than guessing:
- ed25519 only. Another algorithm is reported as
unsupported_alg, never as verified. - Components
@authority,@method,@pathandsignature-agent. A signature over anything else cannot be reconstructed, so it is refused. - Key discovery from
/.well-known/http-message-signatures-directory, over HTTPS only. - Key id is the RFC 7638 thumbprint computed from the key itself, so a directory cannot label one key with another's id.
- Clock skew of five minutes is tolerated in both directions; beyond that a signature is expired or not yet valid.
Not implemented: multiple signatures per request, RSA and ECDSA, and derived components beyond the list above. Each would be a branch no current signer exercises — untested code standing between a request and the word “verified”.