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 field
Unsigned
AgentDraft-signed
decision
block
allow
reason
bot
verified_agent_clean
bot score
90
90
spam probability
not computed; the bot block ran first
0.0166
verification id
sf_ver_maRcYpNPyBBNV0rkECAUbw
sf_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,
},
)
{"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.
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.