explainer · 2026-09-27
How to document webhooks so AI agents can verify them
The webhook pages that passed give the header, the secret, the algorithm over the raw body, and a timing-safe check.
A webhook page has to answer two questions. The first is how to receive a delivery. The second is how to check that a delivery came from your product rather than from someone who found the endpoint URL. Many pages answer the first question and stop. A reader who follows such a page ships an endpoint that trusts any caller.
We run the AI Agent Readiness Test and publish one report per product. Between August 5 and September 22, 2026, we tested 93 products. Each product gets five first-hour developer jobs, and the fourth job is one webhook or authentication path. For 63 of the 93 products, that job was verifying an incoming webhook. The other 30 got an authentication path, so this post covers the 63. A panel of three models, GPT 5.6 Sol, Claude Opus 5, and DeepSeek v4 Flash, works through the public documentation pages for each product. Each model works alone with the same brief and no shared context. No accounts are created, no API calls are made, and no code is run.
The job offered 378 points, which is 63 products across 3 models at 2 points for a passing vote. The products lost 80 points, and 31 of the 63 lost at least one. The consensus vote was PASS for 39 products, PARTIAL for 19, and FAIL for 4. One product, Vapi, split three ways, with the three models returning FAIL, PARTIAL, and PASS on verifying a server event.
What the passing pages document
A verification has parts, and a passing page documents all of them. The sender sets a signature header on each delivery. The receiver holds a signing secret and needs to know where that secret comes from. The receiver runs an exact algorithm over the request body, usually an HMAC, a hash-based message authentication code computed with the secret. The receiver compares the result against the signature header without leaking timing. Many products add a timestamp window so an old captured request cannot be replayed. Then the page gives code.
Vercel documents the whole loop. Its page creates the webhook, captures a one-time secret at creation, reads x-vercel-signature, and compares an HMAC-SHA1 of the raw body using crypto.timingSafeEqual. Exa states that the signing secret is only returned when the webhook is created, gives the header format t=/v1=, walks through a four-step procedure, provides runnable Python, JavaScript, and Java, uses a timing-safe comparison, and sets a 300-second replay window.
Linear computes the signature of the request body with the signing secret and compares it against the Linear-Signature header, with raw-body HMAC-SHA256 and a timestamp check. Baseten computes an HMAC-SHA256 of the request body with the secret and compares it. Felt signs each delivery with a felt-signature header that holds the Base64-encoded HMAC-SHA256 of the raw JSON body.
Doppler hashes the request body with the secret and compares the result against the header with a timing-safe equality function. Recall.ai documents signed headers, raw-body HMAC, and a constant-time comparison.
The replay pages state a window. Hermes Agent uses an X-Webhook-Signature-V2 header, an HMAC-SHA256 over timestamp.body, and a timestamp that must be within 300 seconds of the server clock. That window stops a captured request from being replayed later. Zep verifies the raw body and checks a five-minute timestamp.
The strongest pages also cover duplicate deliveries. E2B says a delivery can be retried and the same event may arrive more than once, so the handler must be idempotent and deduplicate by the event id in the payload.
One detail decides whether the check works at all. The hash must run over the exact bytes the server received, before any JSON parsing or re-serialization. Re-serialized JSON produces different bytes, so the signature no longer matches. Notion is the case where this detail breaks a page. Its setup instructions are complete, and it offers a correct SDK helper, but its manual snippets re-serialize parsed JSON before hashing, so the manual path fails. A reader who follows those snippets computes a signature that never matches.
How the other pages fall short
The clearest failure is a page that shows how to receive a delivery and never how to verify one. Daytona documents endpoint creation and the payload shape but names no signature header, no shared secret, and no verification algorithm. Railway documents setup, the payload, and a test button but no signature or authenticity method. Runpod documents submission, acknowledgment, and retries but no payload schema or authenticity check. Mem0 gives payloads but no verification mechanism.
Given such a page, the agent writes a handler that accepts the POST and reads the payload. It ships an endpoint that trusts any caller, and a forged request that reaches the URL is processed as real. A person facing the same gap asks support, opens a dashboard, or sends a test event and inspects the headers. The agent has the pages only.
Some pages get close and lose the receiver step. Supabase documents creation and the payload shape but names no signing secret, no signature header, and no verification procedure. Stytch documents registration and a test delivery but pushes signature verification to linked Svix pages. PostHog covers creation, testing, and IP allowlisting, and its linked source exposes signing without receiver-side verification steps.
Other pages state a verification and leave one detail wrong or unstated, so the check fails. Notion’s manual snippets re-serialize the body before hashing. Radar names an X-Radar-Signature header and an HMAC-SHA1 hash but does not state the digest encoding or the comparison method. A reader cannot implement that check without guessing.
Where this advice stops
The test reads the public pages and nothing else. It creates no accounts, makes no API calls, and runs no code. It therefore cannot tell whether a documented verify method actually works, or whether the secret is delivered as the page describes. A PASS on this job means the models found one clear documented path with no guessing, judged from the pages alone. The panel is three models, which is small. Each report prints every vote with its verbatim quote and the URL of the page it came from, so you can check the evidence against your own documentation. This job applied to the 63 products that send webhooks. The other 30 products had an authentication path as their fourth job, and those findings appear in their own reports.
What to change
Put the whole verification next to the receiving step. Name the signature header. Name the signing secret and say where it comes from, including whether it appears only once at creation. Give the exact algorithm, computed over the raw unparsed body. Give a comparison that does not leak timing. Give a timestamp window against replay. Then give code in the languages your users call from. A page with those parts lets a reader, human or model, accept a delivery safely on the first attempt. Every product’s webhook finding is printed in the reports, and the grades table shows how the scores compare.
Put another docs site through the battery.
Nominate a docs site