Claude API Webhook Signature Verification Guide
Does the Claude API even use webhooks?
If you landed here searching for "claude api webhook signature verification," the short answer is: Anthropic's Messages API doesn't send webhooks. It's a synchronous request/response HTTP API — you send a prompt, you get a completion (or a stream of tokens). There's no event system where Anthropic pushes notifications to a URL you control, so there's no "Claude webhook secret" to verify against.
What you're almost certainly trying to solve is one of two real problems: (1) you're receiving webhooks from a third-party service — Stripe, GitHub, Slack, a payment processor, a CRM — and want to verify those are genuine before triggering a Claude API call in response, or (2) you're building your own async layer on top of Claude (queue a job, call Claude, notify a client via webhook when it's done) and need to sign and verify those outgoing notifications yourself. Both are solvable with the same standard HMAC pattern. Let's walk through it.
The standard webhook signature pattern
Nearly every webhook system (Stripe, GitHub, Slack, Shopify) uses the same shape:
- The sender computes an HMAC-SHA256 hash of the raw request body using a shared secret.
- The signature (and often a timestamp) is sent in a header, e.g.
X-SignatureorStripe-Signature. - Your server recomputes the hash on the raw body it received and compares it to the header value using a constant-time comparison.
- If they match and the timestamp is recent, the request is trusted.
The two most common mistakes: verifying against a body that's already been parsed/re-serialized by a framework (which changes whitespace and breaks the hash), and skipping replay protection so an old captured request can be resent indefinitely.
Verifying an incoming webhook before calling Claude
Here's a generic Express handler that verifies a signed webhook and only then calls Claude to process the payload — a common pattern for "when X happens, ask Claude to summarize/classify/respond":
import express from "express";
import crypto from "crypto";
const app = express();
// Capture the raw body — do NOT use express.json() here
app.use("/webhooks/incoming", express.raw({ type: "application/json" }));
app.post("/webhooks/incoming", async (req, res) => {
const signature = req.headers["x-signature"];
const timestamp = req.headers["x-timestamp"];
const secret = process.env.WEBHOOK_SECRET;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${req.body}`)
.digest("hex");
const valid =
signature &&
crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
if (!valid) return res.status(401).send("invalid signature");
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(401).send("stale timestamp");
}
const event = JSON.parse(req.body);
// Signature verified — safe to act on this event
res.status(200).send("ok");
processEventWithClaude(event); // fire-and-forget, don't block the ack
});
Key details:
express.raw()preserves the exact bytes used to compute the signature.crypto.timingSafeEqualavoids timing attacks that can leak the secret byte-by-byte.- Acknowledge the webhook fast, then process asynchronously — most senders retry on timeout, and a slow Claude call shouldn't block the HTTP response.
Signing your own outgoing webhooks for async Claude jobs
If you're the one notifying a client after a long-running Claude task (document analysis, agentic tool-use chains, batch summarization), sign the payload the same way so your clients can verify it came from you:
function signPayload(payload, secret) {
const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify(payload);
const signature = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest("hex");
return { body, headers: { "X-Signature": signature, "X-Timestamp": timestamp } };
}
Each client gets its own secret, generated at onboarding and rotatable from your dashboard. Store secrets hashed at rest if you can, and never log raw payloads that include them.
Where SubToAPI fits into this
The Claude call itself — the part that happens after you've verified a signature and decided to act — still needs an API key, rate limiting, and usage visibility, especially if multiple webhook sources are hitting the same Claude backend. SubToAPI turns your Claude access into a standard HTTPS API with sub_live_... keys, so your webhook handler just makes a normal authenticated request:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Summarize this webhook payload"}]
}'
Because every key has its own usage metadata, you can tell exactly which webhook integration is driving Claude spend — useful when you've got Stripe, GitHub, and a support-ticket webhook all triggering Claude calls under one project. Streaming and tool use work the same way as the standard Messages API, so you don't change how you call Claude, only how you authenticate it. See the quickstart, messages, streaming, and tool use docs, and check pricing if you're consolidating several Claude-backed services onto one key system. Start with a free trial at signup.
Security checklist for webhook handlers that call Claude
- Verify the signature on the raw, unparsed body — never on re-serialized JSON.
- Use
crypto.timingSafeEqualor an equivalent constant-time compare, never===. - Require and check a timestamp; reject anything older than a few minutes to block replays.
- Return 401 immediately on failure — don't leak whether the signature or timestamp failed.
- Keep secrets per-integration so a leaked secret only compromises one source.
- Log verification failures (without the payload) so you can spot brute-force attempts.
- Rate-limit the endpoint independently of your Claude API rate limits — webhook floods and Claude quota exhaustion are separate failure modes.
questions
Does Anthropic's Claude API send webhooks I need to verify? No. The Messages API is synchronous request/response. If you're verifying a signature, it's from a third-party webhook source or your own async notification system, not from Anthropic directly.
What's the safest way to compare webhook signatures? Use a constant-time comparison function like Node's crypto.timingSafeEqual, and always compute the hash over the raw request body before any JSON parsing or middleware touches it.
Can I use the same signing pattern for my own Claude-powered webhook notifications? Yes — HMAC-SHA256 over the payload plus a timestamp, sent in custom headers, is the standard approach used by Stripe, GitHub, and Shopify, and it works just as well for notifying clients when an async Claude job finishes.