← Blog

How to Set Up Claude API Webhooks (The Right Way)

2026-10-28 · 5 min read · SubToAPI Team

Does the Claude API support webhooks?

No — not natively. Anthropic's Claude API is a synchronous HTTP endpoint: you send a request to /v1/messages, and you get a response (or a stream) back on the same connection. There's no built-in mechanism for Claude to call your server when a job finishes, the way Stripe or GitHub fire webhooks on events.

That said, "how to set up Claude API webhooks" is usually really a question about how to get asynchronous, event-driven notifications when a Claude-powered task completes — especially for long-running jobs, batch processing, or multi-step agent workflows where you don't want a client sitting on an open HTTP connection. The good news: you can build that pattern yourself with a small amount of infrastructure, and it's a well-understood problem. This guide walks through exactly how.

Why you need this pattern

Common scenarios where people reach for "webhooks" around Claude:

In all these cases, the fix isn't a Claude feature — it's wrapping Claude calls in a job queue and emitting your own webhook when the job resolves.

The architecture

Client → Your API → Job Queue → Worker → Claude API
                                   ↓
                              Your webhook sender → Customer's webhook URL
  1. Client submits a job. Your API accepts a request, stores it (e.g., in Postgres or Redis), and returns a job ID immediately.
  2. A worker picks up the job, calls Claude's /v1/messages endpoint (streaming or not), and processes the result.
  3. On completion (success or failure), the worker POSTs a signed payload to the webhook URL registered for that job or customer.
  4. The receiver verifies the signature, acknowledges with a 200, and processes the event.

Minimal worker example (Node.js)

import crypto from "node:crypto";

async function processJob(job) {
  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: 1024,
      messages: [{ role: "user", content: job.prompt }],
    }),
  });

  const result = await response.json();

  await sendWebhook(job.webhookUrl, {
    event: "claude.completion.finished",
    jobId: job.id,
    status: response.ok ? "completed" : "failed",
    result: response.ok ? result : null,
  });
}

async function sendWebhook(url, payload) {
  const body = JSON.stringify(payload);
  const signature = crypto
    .createHmac("sha256", process.env.WEBHOOK_SECRET)
    .update(body)
    .digest("hex");

  await fetch(url, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-signature": signature,
    },
    body,
  });
}

This is the core pattern: queue → call Claude → sign and deliver the result. Everything below hardens it for production.

Signing and verifying payloads

Never let a webhook receiver trust an unsigned POST. Sign the body with HMAC-SHA256 using a secret both sides know, and verify it on receipt:

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

Compare raw buffers, not JSON-parsed objects — whitespace or key ordering differences will break naive string comparisons.

Retries and idempotency

Webhook delivery is unreliable by nature — the receiving server might be down, slow, or return a 500. Build retries with exponential backoff (e.g., 1s, 5s, 30s, 2m, 10m) and give up after a fixed number of attempts, logging the failure for manual replay.

On the receiving end, include a unique eventId in every payload and have the receiver store processed IDs so a retried delivery doesn't double-process a job. This matters more than it sounds — most webhook bugs in production are duplicate-processing bugs, not delivery failures.

When you don't need this at all

If your use case is "show the user the response as it's generated," you don't need webhooks — you need streaming. Polling or webhook-waiting adds latency for no benefit when a direct streamed response works. This is one of the reasons SubToAPI exists: it exposes your existing Claude access as a standard HTTPS API with native streaming support, so for interactive use cases you skip the queue-and-callback dance entirely and just stream tokens back to the client. See /docs/streaming for the request format.

Webhooks earn their place when the job is genuinely async — batch jobs, background enrichment, multi-step tool-use chains that run independently of a user's open tab. For those, the queue + signed callback pattern above is the standard, boring, reliable solution, and it works whether you're calling Claude directly or through SubToAPI's Messages endpoint.

Putting it together with tool use

If your "webhook-worthy" job involves Claude calling tools across multiple turns (e.g., look up data, call a function, reason, call another function), run the entire tool-use loop inside the worker before firing the webhook. Don't fire a webhook per tool call — that creates noisy, hard-to-reconcile event streams. Fire one webhook per logical job: started (optional) and completed/failed.

curl -X POST https://your-api.com/webhooks/claude \
  -H "content-type: application/json" \
  -H "x-signature: $COMPUTED_SIGNATURE" \
  -d '{
    "event": "claude.completion.finished",
    "jobId": "job_8f2a",
    "status": "completed",
    "result": { "content": [{ "type": "text", "text": "..." }] }
  }'

If you're building this on top of SubToAPI, your application keys (sub_live_...) and usage metadata are already centralized in one dashboard, so the worker side of this architecture only needs one set of credentials regardless of which team member's underlying Claude access is being used. Start with the quickstart or check /pricing if you're scaling this across a team.

questions

Does Anthropic plan to add native webhooks to the Claude API? There's no public commitment to this. The API is designed around synchronous request/response and streaming, so for now, async notification patterns have to be built on top, as described above.

Can I use a serverless function instead of a worker queue? Yes — AWS Lambda, Cloudflare Workers, or similar can run the Claude call and fire the webhook, but watch execution time limits if the Claude response is slow; a proper queue with retries is more reliable for anything beyond simple jobs.

How do I test my webhook receiver without running real Claude jobs? Write a small script that posts a fake signed payload matching your real event shape to your receiver's URL — this validates signature verification and idempotency logic independently of Claude API latency or cost.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →