New — webhooks your AI agents can wait on. Pro & Scale opening to early access.
4 min read

Verifying Webhook Signatures Inside an MCP Server

When an AI agent acts on a webhook, an unverified payload is an injection vector. Here's how HMAC webhook signatures work (Stripe, GitHub), why timing-safe comparison matters, and how to verify a callback from inside an MCP server before the agent acts.

SecurityMCPWebhooksAI Agents
O

Ozer

Developer & Founder of HookSense

When a human reads a webhook in a dashboard, a forged payload is a curiosity. When an AI agent acts on one — ships the deploy, refunds the charge, approves the request — a forged payload is a command injection. Anyone who learns your callback URL could steer the agent. That's why signature verification matters more, not less, in the agent era.

This post explains how HMAC webhook signatures work, why the comparison has to be timing-safe, and how to verify a callback from inside an MCP server before your agent trusts it.

Why signatures matter for agents specifically

A callback URL is a secret-ish capability, but URLs leak — into logs, proxies, screenshots, an agent's own transcript. Treat "knows the URL" as "the public." A signature is the real proof: the sender computes an HMAC of the exact payload using a shared secret, and you recompute it on arrival. If they match, the payload is authentic and untampered. If your agent acts without that check, you've wired an unauthenticated instruction straight into a tool-using loop.

How HMAC webhook signatures work

The sender and you share a secret. For each delivery the sender sends a header containing an HMAC (usually SHA-256) of the raw request body. Two common shapes:

  • Stripe — header Stripe-Signature: t=1730000000,v1=5257a8.... You sign the string {timestamp}.{raw_body} and compare the v1 value; the timestamp also guards against replay (reject if it's too old).
  • GitHub — header X-Hub-Signature-256: sha256=7d38cd.... You sign the raw body and compare after the sha256= prefix.

Same idea, different envelopes: recompute the HMAC over the bytes you received and check it against what arrived.

The raw-body gotcha

The single most common verification bug: signing the parsed body instead of the raw bytes. The signature is computed over the exact bytes sent. If your framework parses JSON and you re-serialize it, key order and whitespace change, the bytes change, and a valid signature fails.

// WRONG — re-serialized JSON, bytes differ from what was signed
verify(JSON.stringify(parsedBody), signature, secret)

// RIGHT — verify against the exact raw body
verify(rawBodyBuffer, signature, secret)

Why the comparison must be timing-safe

Once you've recomputed the expected HMAC, you compare it to the one that arrived. A normal === short-circuits at the first differing character — so a forged signature that gets the first few characters right returns very slightly slower to fail. Over many attempts, that timing difference leaks how much of the prefix matched, letting an attacker reconstruct a valid signature byte by byte.

A timing-safe comparison (Node's crypto.timingSafeEqual) always takes the same time regardless of where the strings differ, closing the side channel. Always use it for signatures, tokens, and any secret comparison.

Verifying from inside an MCP server

Doing all of this — picking the provider scheme, recomputing against the raw body, timing-safe compare, replay window — inside every agent is error-prone. HookSense does it at ingest and exposes a verify_signature tool so the agent can confirm authenticity explicitly before acting:

// configure the endpoint's secret once, then:
verify_signature({ slug: "payments-prod", requestId: "evt_abc" })
  -> { valid: true, provider: "stripe" }

Under the hood it runs a timing-safe HMAC check against the endpoint's configured secret (Stripe, GitHub, Shopify, or custom). The agent's rule becomes simple: if not valid, don't act.

Checklist for agent-safe callbacks

  • Verify the HMAC against the raw body, not a re-serialized copy.
  • Use a timing-safe comparison.
  • Honor the provider's replay window (reject stale timestamps).
  • Use the event ID for idempotency — retries resend the same event.
  • Make "valid signature" a precondition for any agent action.

Want this handled for you? HookSense verifies and decrypts every callback before your agent sees it, and the MCP server ships verify_signature out of the box. Start at hooksense.com — see also How AI Agents Receive Async Callbacks Without Polling.

Related posts

Related terms

Try HookSense Free

Inspect, debug, and replay webhooks in real-time. No credit card required.

Get Started Free