Claude API Webhook Integration Tutorial (Step-by-Step)
Does the Claude API have webhooks?
No — the Claude API is a request/response HTTP API. You send a message, you get a completion back (or a stream of tokens). There's no built-in mechanism where Anthropic pushes events to a URL you register, unlike payment processors or GitHub.
What developers usually mean by "Claude API webhook integration" is one of two things: triggering Claude from an incoming webhook (a GitHub push, a Stripe event, a form submission) or notifying another system when Claude finishes generating a response, especially for long-running jobs. Both are easy to build yourself with a thin integration layer. This tutorial walks through that pattern end to end.
The architecture
A typical Claude webhook integration has three parts:
- Inbound webhook receiver — an HTTP endpoint that accepts events from an external system (Slack, GitHub, Zapier, your own app).
- Claude call — the receiver extracts relevant data from the event, builds a prompt, and calls the Messages API.
- Outbound webhook (callback) — once Claude responds, you POST the result to another URL, which could be the same system or a different one (e.g., post a Slack message, update a ticket, write to a database and notify a dashboard).
For short requests this can all happen inside one HTTP handler. For anything that takes more than a few seconds, you should acknowledge the inbound webhook immediately and process the Claude call asynchronously, then fire the outbound webhook when it's done — exactly like how Stripe or GitHub expect you to respond fast and do work later.
Step 1: Build the inbound receiver
Here's a minimal Express endpoint that receives a webhook, verifies its signature, and queues work:
import express from "express";
import crypto from "crypto";
const app = express();
app.use(express.json());
app.post("/webhooks/inbound", (req, res) => {
const signature = req.headers["x-webhook-signature"];
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
if (signature !== expected) {
return res.status(401).send("invalid signature");
}
// Acknowledge immediately, process async
res.status(202).send("accepted");
processWithClaude(req.body).catch(console.error);
});
app.listen(3000);
Always verify signatures on inbound webhooks. Most providers (GitHub, Stripe, Slack) sign their payloads with HMAC — don't skip this check just because it's "internal."
Step 2: Call Claude and extract a response
Here's the processing function using the Messages API directly:
async function processWithClaude(event) {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 500,
messages: [
{
role: "user",
content: `Summarize this event and suggest a next action:\n${JSON.stringify(event)}`,
},
],
}),
});
const data = await response.json();
const result = data.content[0].text;
await sendOutboundWebhook(result, event);
}
If you're running this behind SubToAPI instead of calling Anthropic directly, the call looks almost identical — you swap the endpoint and auth header, and you also get usage metadata (tokens, cost) back on every response, which is useful for logging alongside each webhook event:
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": 500,
"messages": [{"role": "user", "content": "Summarize this event..."}]
}'
See /docs/messages for the full request/response shape.
Step 3: Fire the outbound webhook
Once you have Claude's output, POST it wherever it needs to go:
async function sendOutboundWebhook(result, originalEvent) {
await fetch(process.env.OUTBOUND_WEBHOOK_URL, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
source_event_id: originalEvent.id,
summary: result,
timestamp: new Date().toISOString(),
}),
});
}
Include the original event ID so the receiving system can correlate the callback with the request that triggered it — this matters a lot once you have retries in play.
Handling retries and idempotency
Two failure modes to design for:
- Claude call fails or times out. Wrap it in retry logic with exponential backoff, and cap retries (3–4 attempts is reasonable). Log failures so you can replay them manually.
- Outbound webhook fails. The receiving system might be down. Store the result and retry the callback separately from the Claude call — don't re-generate the response just because the delivery failed.
A simple way to make this safe: persist {event_id, status, result} in a database row as soon as Claude responds, then have a separate small worker that retries undelivered webhooks on a schedule. This decouples "did Claude finish" from "did the callback get delivered."
Streaming inside a webhook flow
If you want token-by-token updates (e.g., streaming into a chat UI that's itself driven by a webhook trigger), you can't stream over a single outbound webhook POST — webhooks are one-shot. Instead, stream the Claude response server-side and push partial updates over a WebSocket or Server-Sent Events connection to the client, while using the webhook only as the trigger. SubToAPI's streaming endpoint works the same way Anthropic's does under the hood — see /docs/streaming if you're building this.
Where SubToAPI fits
If you're integrating Claude into several webhook-driven workflows (CI pipelines, support ticket triage, form processing), managing raw Anthropic keys across each service gets messy fast — no per-service usage visibility, no easy key rotation, no team access control. SubToAPI wraps the same Claude models behind sub_live_... API keys with per-key usage metadata and team seats, so each webhook integration can have its own scoped key and you can see exactly which one is driving cost. Start with a free trial at /signup, or check /pricing and /docs/quickstart to see how the setup compares to calling Anthropic directly. Tool use (function calling) works the same way too — see /docs/tools if your webhook handler needs Claude to call external functions as part of the flow.
Questions
Does Anthropic offer native webhook events for the Claude API? No. The API is purely request/response (or streaming within a single connection). Any "webhook" behavior — triggering on an external event or notifying another system when a response is ready — has to be built in your own integration layer.
How do I handle Claude calls that take longer than my webhook timeout? Acknowledge the inbound webhook immediately with a 2xx response, then process the Claude call asynchronously in a background job or queue, and deliver the result via a separate outbound webhook when it's ready.
Can I verify that an outbound webhook I send was actually delivered? Only if the receiving endpoint responds with a success status you check. Treat non-2xx responses as failures, log them with the original event ID, and retry on a schedule rather than blocking the Claude call on delivery success.