Siftfy verifies an AgentDraft signature before it decides the message.
A legitimate agent submitting a form usually looks like a bot: headless browser, cloud address, no keystrokes. Siftfy Content Gate scores that traffic 90 and blocks it before the spam classifier sees the text. AgentDraft gives the agent an Ed25519 identity; Siftfy verifies it at siteverify, exempts the bot block, and still classifies the content. In the production run the bot score stayed 90 and the signed request was allowed with a spam probability of 0.0166.
Disclosure: Siftfy is a separate product, not an AgentDraft customer. This is a joint engineering case study, not an independent customer endorsement. The comparison used a throwaway enforce site; the site was deleted and the signing key rotated afterwards.
Updated
Why does a legitimate agent get blocked?
Agent traffic and scraper traffic share the same surface signals. A cloud address, a scripted User-Agent, and text that arrived without keystrokes are enough for a bot gate to score the session as automation. On a Siftfy enforce site, a score of 90 or more is refused before the content classifier runs. The gate is right that the request is automated. It has no way to tell a booking agent from a scraper.
The usual workarounds move the problem. 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 missing input is a verifiable identity for the automated client.
What does Siftfy verify at siteverify?
Web Bot Auth is an IETF profile built on RFC 9421 HTTP Message Signatures . AgentDraft issues one Ed25519 key per agent 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. The signature lives at most five minutes.
The verifier is deliberately narrow. 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 failed check 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.
What the signature proves is narrow too. It 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 the directory publishes; Siftfy returns it and does not independently verify it.
What happened in the production run?
The same scripted client sent the same two-night availability question twice to a throwaway enforce site. The unsigned request was blocked before inference. The signed request was allowed, and the classifier still returned a score.
| 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 carry the story. The bot score stayed 90, so the signature exempted the bot block and left the scoring alone. 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.
Captured 2026-10-25 · throwaway enforce site · the site was deleted and the key rotated after the run; the key id is historical and no longer verifies.
How does an agent sign the request?
Mint a Web Bot Auth key in the AgentDraft dashboard under Agents → Signing. The private JWK is shown once; AgentDraft stores only the public half. Then sign the outbound request locally and send the four signed headers. AgentDraft is not on the request path.
from agentdraft import agent_signature_fields, sign_request
headers = sign_request(
method="POST",
url="https://forms.example.com/contact",
private_jwk=private_jwk, # the one-time value from the dashboard
agent_id="agt_01...",
body=body,
)
agent_signature = agent_signature_fields(
headers, method="POST", authority="forms.example.com", path="/contact"
)The destination site forwards the original method, authority, path, and the four signed headers to Siftfy as agent_signature. It also sets verified_agent_policy to allow or review on the action. ignore is the default and fetches no directory; allow and review require Siftfy Pro. A site can start in review, inspect the agent traffic, then move to allow without changing the agent's key.
What does the signature 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, which is also the denial-of-service boundary. A site opts in one action at a time.
- Rotation and plan changes take effect at the decision. AgentDraft replaces the public JWK on rotation, and Siftfy re-checks the owner's plan, so a downgrade disables the bypass without a migration. A cached directory expires within 300 seconds.
Where can builders read the source material?
- Siftfy's side of the story : the site-owner view, the policy values, and the same production run.
- Siftfy Content Gate integration guide : the siteverify contract, the verified-agent object, and the Pro entitlement.
- Agent identity evidence : the live block-to-allow capture, the key model, and the limits.