joint case study · verified agents

A signed agent gets past the bot block. The spam check still runs.

Updated October 26, 2026

An AI agent asked about a two-night stay from a cloud container. It solved the challenge, sent a normal question, and Siftfy blocked it anyway: bot score 90, decision block, reason bot. The gate was right about the automation and wrong about the request. AgentDraft signed the same request with an Ed25519 key, and Siftfy returned allow with reason verified_agent_clean and a spam probability of 0.0166. The bot score stayed 90.

Joint engineering case study. Siftfy is a product of VectraSEO LLC. AgentDraft and Siftfy are separate products in the same portfolio; this is not an independent customer endorsement. The comparison below is a real production run on a throwaway enforce site with a production AgentDraft key. The site was deleted and the key rotated afterwards, so the key id in the capture is historical and no longer verifies.

90bot score in both runs
block → allowsame content, one signature
0.0166spam probability, signed run
1Ed25519 key per agent

The wall

Most agent traffic looks the same to a bot gate: a cloud address, no mouse movement, no keystrokes, a form filled faster than a person can type. Siftfy's bot-rules-v2 scores that 90 or more, and on an enforce site the request is refused before the spam classifier sees the text.

The usual fixes are bad. An IP allowlist trusts whoever rents the next address. A User-Agent allowlist trusts a string the client chooses. Turning off the bot check lets scrapers through with the agents. The gate needs an identity it can verify without calling the agent's vendor on every request.

What Siftfy verifies

Web Bot Auth is an IETF profile built on RFC 9421 HTTP Message Signatures. AgentDraft gives each agent an Ed25519 key and publishes the public half at a per-agent directory. The agent signs the request method, authority, path, signature-agent header, and a SHA-256 digest of the body. Siftfy fetches the directory named in Signature-Agent, checks that the key is listed, and verifies the signature over those exact bindings. A signature lives at most five minutes, so a captured request cannot be replayed later.

The verifier is narrow on purpose. Siftfy accepts Ed25519 only, fetches only HTTPS directories on an operator allowlist (api.agentdraft.io by default), follows no redirects, refuses private and link-local addresses, caps the response at 16 KiB and 8 keys, and caches it for at most 300 seconds. A signature that fails any binding is a verdict, not a submission error: Siftfy returns verified_agent.valid=false with a stable error, and decides the request exactly as it would have without the signature.

The signature proves that a key an allowlisted directory published signed this method, authority, path, and body. It does not prove who operates the agent, whether a human asked for the request, or whether the message is welcome. human_delegated is metadata published by the directory; Siftfy returns it and does not independently verify it.

The production run

Captured 25 Oct 2026 · 22:01 UTC on a throwaway enforce site. The same scripted client sent the same two-night availability question twice.

siteverify fieldUnsignedAgentDraft-signed
decisionblockallow
reasonbotverified_agent_clean
bot score9090
spam probabilitynot computed; the bot block ran first0.0166
verification idsf_ver_maRcYpNPyBBNV0rkECAUbwsf_ver_rS5CXDApIdrGx1eEmF-vhw

Two details matter more than the green allow. The bot score stayed 90: the signature exempted the bot block, not the scoring. And spam_probability is present because the content classifier still ran. A prompt-injected agent with a valid key can still send spam; the site will see which agent did it.

Provenance: the site was deleted and the AgentDraft signing key was rotated after the run. The agent id and both verification ids are from the capture; the key id it used is historical and no longer verifies.

How to wire it

Two roles do the work. The agent signs the request; the site chooses what a valid signature is allowed to skip.

Agent side: sign the request

Mint a Web Bot Auth key in AgentDraft under Dashboard → Agents → Signing. The private JWK is shown once; AgentDraft stores only the public half. Then sign the outbound request with the Python or TypeScript SDK and send the four signed headers. AgentDraft is not on the request path.

pythonSign the request with the AgentDraft SDK
from agentdraft import agent_signature_fields, sign_request

# private_jwk is the one-time value from Dashboard -> Agents -> Signing.
headers = sign_request(
    method="POST",
    url="https://forms.example.com/contact",
    private_jwk=private_jwk,
    agent_id="agt_01...",
    body=body,
)

# Send the four headers with the request. The destination site forwards
# them to Siftfy as agent_signature.
agent_signature = agent_signature_fields(
    headers,
    method="POST",
    authority="forms.example.com",
    path="/contact",
)

Site side: opt the action in

Set verified_agent_policy on the action. ignore is the default and fetches no directory. allow exempts a verified agent from the bot block and lets the classifier decide. review does the same but holds a clean submission for a human. allow and review require Siftfy Pro; ignore is available on every plan.

The backend forwards the original method, authority, path, and the four signed headers to siteverify as agent_signature. Reconstructing any of those fields from something else verifies a different message.

pythonForward the signature to Siftfy siteverify
import httpx

# The backend already has the original request. Preserve these fields.
agent_signature = {
    "method": request.method,
    "authority": request.headers["host"],
    "path": request.url.path,
    "signature_input": request.headers["signature-input"],
    "signature": request.headers["signature"],
    "signature_agent": request.headers["signature-agent"],
    "content_digest": request.headers["content-digest"],
}

resp = httpx.post(
    "https://api.siftfy.io/v1/content-gate/siteverify",
    json={
        "challenge": challenge,
        "proof_nonce": proof_nonce,
        "expected_action": "contact_submit",
        "expected_origin": "https://www.example.com",
        "content": content,
        "agent_signature": agent_signature,
    },
)
jsonThe agent_signature object siteverify receives
{
  "challenge": "<siftfy_content_gate_challenge>",
  "proof_nonce": "<siftfy_content_gate_proof>",
  "expected_action": "contact_submit",
  "expected_origin": "https://www.example.com",
  "content": "Hi, I'd like to ask about availability for two nights in March...",
  "agent_signature": {
    "method": "POST",
    "authority": "www.example.com",
    "path": "/contact",
    "signature_input": "sig1=(\"@method\" \"@authority\" \"@path\" \"signature-agent\";key=\"sig1\" \"content-digest\");created=1792944000;expires=1792944060;keyid=\"<thumbprint>\";tag=\"web-bot-auth\"",
    "signature": "sig1=:<base64 Ed25519 signature>:",
    "signature_agent": "sig1=\"https://api.agentdraft.io/v1/agents/agt_.../http-message-signatures-directory\";type=jwks_uri",
    "content_digest": "sha-256=:<base64 sha-256 of content>:"
  }
}

Read verified_agent from the response, at the top level or inside bot. When valid is false, the submission is decided as before and the error says why.

What identity does not buy

  • Only the bot block is exempted. The deny list, solver reputation, velocity limits, and the BERT spam classifier still run, in that order. A verified agent that sends spam is still blocked or reviewed.
  • A valid key is not a human.human_delegated is directory metadata. A compromised agent can sign spam, and a prompt-injected agent can sign a request its operator never intended.
  • The policy is per action and off by default.ignore fetches no directory at all, which is also the DoS boundary. A site can start with review and watch agent traffic before allowing it.
  • Downgrades disable the bypass. The decision re-checks the owner's plan, so a site that drops Pro stops exempting verified agents without a migration. The stored policy survives a re-upgrade.
  • Key rotation is immediate. AgentDraft replaces the public JWK on rotation and the old kid stops verifying. A cached directory expires within 300 seconds; an unavailable directory refuses the exemption, not the submission.
  • The directory is public by design. Siftfy stores the verdict, key id, directory URL, and the metadata the directory publishes. It stores no key material and adds no identifier to the submitted content.

Why both sides care

Agent builder

A way through the gate

An agent that submits forms or asks for a quote stops failing at the first cloud-IP check. The agent still has to send a clean message; the signature only gets it to the classifier.

Site owner

A verifiable identity, not an allowlist

A site can let known automation through without trusting a User-Agent string or switching off bot protection. It can start in review, see which agent called, and keep the spam decision.

Both sides keep the narrow claim: identity moves a request past the automation block. It does not move the content past the classifier.

How to get it

Web Bot Auth signing keys, A2A cards, MCP client identity, and signed receipts are on AgentDraft Team and above. The verified-agent policy is on Siftfy Pro. The Siftfy side ships off by default, so a site can turn it on for one action, watch it in review, and return it to ignore without changing the agent's key.

agentdraft.io

Read the other half

AgentDraft's side of the story: the verifier contract and the production run

The agent-builder view covers the Ed25519 key, the four signed headers, the live block-to-allow run, and the same 0.0166 classifier result.

Read the AgentDraft case study →

Verified agents: common questions

Does a signed agent bypass spam filtering?

No. A verified agent can skip the bot block only, and only when the action's verified_agent_policy is allow or review. The deny list, solver reputation, velocity limits, and the content classifier still run in that order. The production run on this page shows the bot score staying at 90 while the signed request was allowed with a spam probability of 0.0166.

What is Web Bot Auth?

Web Bot Auth is an IETF profile built on RFC 9421 HTTP Message Signatures. An automated client signs its request with a private key and names a public key directory in the Signature-Agent header. A verifier fetches that directory, checks the signature against the request, and learns which key signed it without trusting a User-Agent string or an IP range.

Is the verified-agent exemption on by default?

No. verified_agent_policy defaults to ignore, which ignores agent_signature and fetches no directory. allow and review require the Siftfy Pro plan. A site can turn the exemption on for one action, watch it in review, and return it to ignore without changing the agent's key.

What happens when a signature is invalid or expired?

Siftfy refuses the exemption, not the submission. siteverify returns verified_agent with valid false and a stable error such as expired, bad_signature, content_digest_mismatch, key_not_found, or signature_agent_not_allowed. The request is then decided exactly as it would have been without a signature.

Does Siftfy trust the agent's stated intent?

No. The signature proves that a key published by an allowlisted directory signed this method, authority, path, and body. It does not prove who operates the agent, whether a human asked for the request, or whether the message is welcome. human_delegated is metadata published by the directory; Siftfy returns it and does not independently verify it. That is why the content classifier still runs.

Set up verified agents

Start with review on one action. Content Gate reference, or read the live demo.