TypeScript SDK v4 is now available! See what's new
Background Jobs

Build reliable webhooks

Handle webhooks and AI model callbacks with fast acknowledgement, at-least-once delivery, and replay.

Webhooks are how AI providers notify your app when async work completes, whether that's LLM inference, embedding generation, or image synthesis from providers like Replicate, fal.ai, or Together AI. The same pattern covers payment processors, e-commerce platforms, and CRMs. They all share the same failure modes: slow acknowledgement triggers retries, burst traffic drops events, and downtime means lost results you can't recover without re-running the job.

Reliable webhooks need to do three things: respond quickly, handle varying throughput, and recover from downtime. Most webhook providers will mark a delivery as failed if your endpoint doesn't respond within a few seconds. Your application also needs to handle bursts of requests without crashing and properly re-process any events that fail.

§What reliable webhook handling requires

Whatever you build, you need to cover four things:

  • Fast acknowledgement. Most providers retry after 3–10 seconds. Your endpoint must respond well under that, usually by handing the payload off before doing any real work.
  • Signature verification. Verify the provider's signature before you trust the payload. Stripe, GitHub, and Shopify all sign deliveries differently, and a missed verification is a security issue, not a reliability one.
  • At-least-once delivery handling. Most providers retry on transient failures. Your handler has to be idempotent on the natural key the provider supplies (event ID, delivery ID).
  • Replay. When you ship a bug and 10,000 events failed silently in production, you need to replay them against the new code without asking the provider to resend.

In a hand-rolled system, that's a webhook handler, a queue (Redis/SQS/RabbitMQ), a worker process, a dead-letter queue, a log archive, and a replay job. Each adds its own operational surface.

§With Inngest

Inngest combines an event hub with a job scheduler. It's designed to receive large volumes of events via HTTP and automatically call functions that match those events. You can set up a webhook by creating a webhook source in the Inngest dashboard, which gives you a unique URL to add to any service that supports webhooks.

After connecting your webhook, events start flowing into your Inngest dashboard. Here's an example processing a Stripe payment failure:

typescript
01import { inngest } from "./client";
02
03export const handlePaymentFailed = inngest.createFunction(
04 { id: "handle-payment-failed", triggers: [{ event: "stripe/invoice.payment_failed" }] },
05 async ({ event, step }) => {
06 const invoice = event.data.data.object;
07
08 const user = await step.run("load-user", async () => {
09 return await getUserByStripeCustomerId(invoice.customer);
10 });
11
12 await step.run("downgrade-plan", async () => {
13 await billingUtils.downgradeUser(user.id);
14 });
15
16 await step.run("send-notification", async () => {
17 await sendDowngradeEmail(user.email);
18 });
19
20 return `Downgraded user ${user.id}`;
21 }
22);

Every event is persisted and observable in the dashboard. If a function fails, it retries automatically. If you deploy a fix after a bug, you can replay failed events without asking the webhook provider to resend them.

For AI model callbacks, the same approach applies. When a long-running inference job completes and the provider sends a webhook, Inngest receives the event, triggers your function, and handles retries if any step fails:

typescript
01export const handleModelCallback = inngest.createFunction(
02 { id: "handle-model-result", triggers: [{ event: "ai/inference.completed" }] },
03 async ({ event, step }) => {
04 const result = await step.run("process-result", async () => {
05 return await processModelOutput(event.data.output);
06 });
07
08 await step.run("store-result", async () => {
09 await db.results.create({
10 data: {
11 jobId: event.data.jobId,
12 output: result,
13 model: event.data.model,
14 },
15 });
16 });
17 }
18);

Functions can be deployed to several supported platforms.

§Alternative approaches

  • AWS API Gateway + SQS + Lambda. Works, but you're now wiring three services together and writing your own retry/dead-letter routing. Replay means querying CloudWatch logs and re-publishing manually.
  • Hosted webhook gateways (Hookdeck, Svix). Closer to what Inngest does for webhook ingestion, but they hand off to your own job runner, so you still need a durable execution layer downstream.
  • Direct in your API. Skipping the queue entirely. Fine until your endpoint stalls for 30 seconds during an LLM call and the provider stops delivering.

§Additional Resources