Claude API Webhook Integration Setup Guide
Does the Claude API have webhooks?
No — the Claude API is a synchronous request/response API. You send a prompt, you get a completion (or a stream of tokens) back on the same connection. There's no built-in event system that pushes notifications to a URL when something happens on Anthropic's side.
So when developers search for "Claude API webhook integration," they're almost always trying to solve one of two real problems: triggering a Claude call when an external event happens (a new Stripe payment, a GitHub PR, a Slack message), or notifying another system when a Claude response is ready, especially for long-running or background jobs where you don't want to hold a connection open. This guide covers both patterns with working code.
Pattern 1: Inbound webhooks that trigger Claude
This is the most common setup. Something happens in a third-party system, it POSTs to your server, and your server calls Claude to process it.
Typical flow:
- Receive and verify the webhook payload
- Extract the relevant data (an email body, a support ticket, a commit diff)
- Call the Claude API with that data as context
- Do something with the result — save it, reply, forward it
Here's a minimal Express endpoint that receives a webhook and calls Claude:
import express from "express";
import crypto from "crypto";
const app = express();
app.use(express.json({ verify: rawBodySaver }));
function rawBodySaver(req, res, buf) {
req.rawBody = buf;
}
function verifySignature(req, secret) {
const signature = req.headers["x-webhook-signature"];
const expected = crypto
.createHmac("sha256", secret)
.update(req.rawBody)
.digest("hex");
return signature === expected;
}
app.post("/webhooks/incoming", async (req, res) => {
if (!verifySignature(req, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("invalid signature");
}
// Acknowledge immediately, process async
res.status(202).send("accepted");
const payload = req.body;
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [
{ role: "user", content: `Summarize this support ticket:\n\n${payload.text}` },
],
}),
});
const data = await response.json();
await saveResult(payload.id, data.content[0].text);
});
app.listen(3000);
Two details matter here. First, verify the signature before trusting the payload — never call an LLM with unverified input from a public endpoint that also has your API key attached to it. Second, acknowledge the webhook immediately and process it asynchronously. Most webhook providers (Stripe, GitHub, Slack) expect a response within a few seconds and will retry if you don't reply, which can lead to duplicate Claude calls if your processing takes longer than that.
Pattern 2: Outbound "completion" webhooks
If you're running Claude calls in a background job — batch summarization, document processing, a queue worker — you often want to notify another service when the result is ready instead of polling. This isn't something Claude provides natively; you build it yourself around the API call.
async function processJobWithCallback(job) {
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 2048,
messages: job.messages,
}),
});
const result = await response.json();
// Notify the requesting system that the job is done
await fetch(job.callback_url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
job_id: job.id,
status: "completed",
output: result.content[0].text,
usage: result.usage,
}),
});
}
This pattern is useful for API products where a client submits a job, gets a job_id back immediately, and receives a webhook when it's done rather than holding a request open for 30+ seconds. Include the job_id, a status field, and usage metadata in the callback payload so downstream systems can reconcile results and track cost per job.
Handling streaming inside a webhook flow
If your webhook consumer needs partial output rather than waiting for the full response, don't try to forward a raw stream through a webhook call — webhooks are single request/response, not persistent connections. Instead, buffer the stream server-side and either send incremental webhook pings at intervals or push chunks over a WebSocket/SSE connection to the actual client. The full mechanics of consuming streamed responses are covered in /docs/streaming.
Retries, idempotency, and failure handling
A production webhook integration needs to handle three failure modes:
- Duplicate deliveries — most providers retry webhooks that don't get a fast 2xx response. Store the webhook's unique event ID and skip processing if you've already seen it.
- Claude API errors — rate limits, timeouts, or malformed input should be caught and retried with backoff, not silently dropped.
- Downstream callback failures — if your outbound webhook call fails, retry a few times with exponential backoff, then fall back to a polling endpoint the client can check.
Where SubToAPI fits
SubToAPI doesn't add webhook events to the Claude API itself, but it gives you a stable, authenticated HTTPS endpoint (sub_live_... keys) to call from your webhook handlers, along with per-request usage metadata you can attach to your own callback payloads — useful for billing jobs back to customers or tracking cost per webhook-triggered call. Streaming and tool use both work the same way as direct API access; see /docs/messages and /docs/tools for request formats, or /docs/quickstart to get a key running in a few minutes. Plans start at €9/month on the Solo tier, with team seats on the Team and Scale plans — details at /pricing.
questions
Does Anthropic's Claude API support native webhooks? No. It's a request/response API with no built-in event push system. Webhook behavior has to be built on your side, either by receiving inbound webhooks that trigger a Claude call or by sending outbound callbacks when your own background job finishes.
How do I avoid duplicate Claude API calls from webhook retries? Store the event ID from each incoming webhook and check it before processing. Return a 2xx response as fast as possible and do the actual Claude call asynchronously so the sender doesn't retry due to a timeout.
Can I stream Claude's response directly through a webhook? Not directly — webhooks are single-shot HTTP calls. Buffer the streamed response server-side, then either send periodic webhook updates or relay chunks to the client over SSE or WebSockets. See /docs/streaming for handling the stream itself.