Reference

What Is a Webhook?

Webhooks are how one system tells another that something happened, without being asked. They are the plumbing under most payment flows, CI pipelines, chat notifications, and event-driven AI agents.

Updated 27 July 2026

In short
A webhook is an automated message one application sends to another the moment an event happens: an HTTP request pushed to a URL you register, carrying data about the event. Unlike an API, which you must poll for updates, a webhook delivers them to you in real time. Webhooks are the standard way to trigger automated workflows and AI agents from external events.
Mechanics

How webhooks work, step by step

A webhook has two sides. The sender is a system where things happen: a payment clears, a branch gets pushed, a ticket changes owner. The receiver is an HTTPS endpoint you control and hand to the sender in advance. Nothing travels between them until the event occurs. Then the sender makes one HTTP POST to your URL with a body describing what changed.

  1. 01

    Register a callback URL

    In the sender’s dashboard or API you supply a URL such as https://api.yourapp.com/webhooks/stripe and select the event types you care about. Most senders hand back a signing secret at this moment. It is shown once. Put it in your secret manager, not in the repository.
  2. 02

    An event happens at the source

    Someone completes a checkout. The sender records the event in its own log first, then queues a delivery for every endpoint subscribed to that type. One event can fan out to a dozen receivers, and each of them succeeds or fails independently.
  3. 03

    The sender POSTs a payload

    The request carries a JSON body, a content type, and a signature header. The body is the whole story: an event id, the event type, a timestamp, and a snapshot of the object that changed.
  4. 04

    You verify, acknowledge, then work

    Check the signature against the raw bytes. Return a 2xx status within a second or two. Push the real processing onto a queue. Senders judge a delivery by your status code, not by whether your business logic finished.
  5. 05

    The sender retries what failed

    A timeout or a non-2xx response puts the delivery back on a retry schedule with widening gaps. That is why your handler has to tolerate seeing the same event more than once.

Here is what a real delivery looks like on the wire. This is a trimmed Stripepayment_intent.succeeded event, the single most common webhook in production software:

POST /webhooks/stripe HTTP/1.1
Host: api.yourapp.com
Content-Type: application/json
Stripe-Signature: t=1753574400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e5
User-Agent: Stripe/1.0 (+https://stripe.com/docs/webhooks)

{
  "id": "evt_3PkQ8sL2eZvKYlo2C1x9Yz4B",
  "object": "event",
  "api_version": "2026-03-31",
  "created": 1753574400,
  "type": "payment_intent.succeeded",
  "livemode": true,
  "data": {
    "object": {
      "id": "pi_3PkQ8sL2eZvKYlo2C1x9Yz4B",
      "object": "payment_intent",
      "amount": 4900,
      "currency": "usd",
      "status": "succeeded",
      "customer": "cus_QeR8tYvBnM2wZx",
      "metadata": { "plan": "team-annual" }
    }
  }
}

Three fields do most of the work. id gives you a deduplication key. type tells your router which handler to call. created lets you reject deliveries that have been sitting in someone else’s replay buffer.

Comparison

Webhook vs API: the difference

An API and a webhook are both HTTP. The difference is who starts the conversation. With an API, your code asks a question and waits for an answer, so freshness costs you a request every time you check. With a webhook, the other system speaks first, and you only spend resources when something actually changed.

API callWebhook
Who initiatesYour codeThe source system
DirectionYou pull dataData is pushed to you
FreshnessAs fresh as your poll intervalSeconds after the event
Cost when idleYou still pay for empty pollsNothing is sent
Failure modeYou retry your own requestSender retries, you may see duplicates
Typical useReading state, taking actionHearing that state changed
Most integrations use both: a webhook to learn that something happened, an API call to fetch the full, current record before acting on it.

The last row matters more than it looks. Payloads are snapshots, and a snapshot can be stale by the time your queue gets to it. For anything financial or irreversible, treat the webhook as a notification and read the authoritative value back from the API.

In the wild

Webhook examples you already use

Stripe emits several hundred event types. Teams subscribe to a handful: payment_intent.succeeded to fulfil an order, invoice.payment_failed to start dunning, charge.dispute.created to freeze an account before the chargeback lands. Stripe retries failed deliveries with exponential backoff for up to three days.

GitHub fires push, pull_request, and workflow_run events at any URL you register on a repository or organisation. Almost every deploy bot, Slack notifier, and third-party CI runner is built on these. GitHub expects your endpoint to answer within ten seconds and does not retry automatically, though every delivery is stored and can be replayed by hand from the repository settings.

Slack delivers workspace activity through its Events API: app_mention when someone tags your bot, message.channels for channel traffic. Slack wants an acknowledgement within three seconds and retries three times if it does not get one, which is why every Slack bot answers immediately and posts its real reply afterwards.

Twilio is the useful odd one out. An inbound SMS arrives as application/x-www-form-urlencoded rather than JSON, with fields named From, To, and Body, and Twilio reads your response body as instructions for what to do next. Not every webhook is JSON, and not every webhook ignores what you send back.

Hardening

Webhook security

A webhook endpoint is a public URL that anyone on the internet can POST to. The signature is the only thing that makes it yours.

Senders sign each delivery with an HMAC computed over the raw request body and a shared secret. Your job is to recompute that HMAC and compare. Three details cause nearly every failed integration and nearly every vulnerability.

import crypto from 'node:crypto';

const TOLERANCE_SECONDS = 300;

// rawBody must be the exact bytes that arrived. Parsing the JSON and
// re-serialising it changes key order and whitespace, and the signature
// stops matching for reasons that take an afternoon to find.
export function verifyDelivery(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((kv) => kv.split('=')),
  );
  const timestamp = Number(parts.t);

  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) {
    return false; // a captured request replayed tomorrow still signs correctly
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  // Never compare with ===. It returns early and leaks the signature
  // one byte at a time to anyone willing to measure.
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(parts.v1, 'hex'),
  );
}

First, sign the bytes you received. Frameworks that parse JSON before your handler runs will quietly break verification, so configure a raw-body parser on the webhook route only. Second, bound the timestamp. Without a tolerance window, a signature captured today stays valid forever and a replayed delivery is indistinguishable from a real one. Third, compare in constant time.

Beyond the signature: accept HTTPS only, keep two secrets valid during rotation so you can roll without dropping deliveries, and scope the endpoint so it can do nothing except enqueue work. IP allowlisting sounds appealing and ages badly, since provider egress ranges change without much notice.

Event-driven AI

Webhooks as AI agent triggers

The pattern that has become standard for production agents is short: an event fires, an agent runs, the result goes back where the work lives. A ticket lands in Zendesk and an agent drafts the reply. An invoice hits an inbox and an agent extracts the line items into the ERP. A deal moves stage in the CRM and an agent assembles the handover brief. In each case the webhook is what turns a passive assistant into something that acts on its own schedule.

The operational shape differs from a normal webhook handler in one way: agent runs take seconds to minutes, far longer than any sender will wait. Acknowledge the delivery immediately, run the agent asynchronously, and write results back through the source system’s API.

In Swfte Studio a workflow can start from a webhook trigger without you standing up a server, and the model calls inside that workflow run through Connect, which fronts 50+ providers behind one API with automatic failover and per-call cost tracking. That matters for event-driven work specifically: a webhook keeps arriving whether or not your primary model provider is having a good day.

Operations

Common webhook problems and how to debug them

Timeouts. The usual cause is doing the work inside the request. Signature check, enqueue, return 200. Anything slower than about a second belongs on a queue, and a handler that calls three internal services before responding will eventually cross whatever limit the sender enforces.

Duplicates. Retries mean at-least-once delivery, so the same event id will arrive twice sooner or later. Deduplicate on the event id with a unique constraint rather than a cache lookup, because two retries can land on two instances at the same moment:

// Every delivery carries a stable event id. Claim it before you act.
const claim = await db.query(
  `INSERT INTO processed_events (event_id, received_at)
   VALUES ($1, now())
   ON CONFLICT (event_id) DO NOTHING
   RETURNING event_id`,
  [event.id],
);

if (claim.rowCount === 0) {
  return res.status(200).send('duplicate ignored');
}

// Acknowledge in milliseconds, then do the slow work off the request path.
await queue.publish('fulfil-order', event.data.object);
return res.status(200).send('ok');

Out-of-order arrival. Parallel delivery workers mean subscription.updated can arrive before the subscription.created that caused it. Never trust arrival order. Sort on a field inside the payload, usually the object’s own version counter or updated timestamp, and drop anything older than what you have already applied.

Silent failures. A webhook that stops arriving looks exactly like a quiet week. Alert on the absence of expected events, keep the sender’s delivery log open when debugging, and log the event id on both sides so a support conversation can start from a shared identifier. Our error encyclopedia and fix guides cover the specific HTTP and TLS failures that show up in delivery logs.

Common questions

What is a webhook in simple terms?
A webhook is a way for one app to tell another app that something just happened. You give the first app a URL; when the event occurs, it sends an HTTP request to that URL with the details. It is often described as a "reverse API" because the data comes to you instead of you asking for it.
What is the difference between a webhook and an API?
An API is a request interface: your code asks for data and gets a response. A webhook is event-driven, so the other system pushes data to you when something changes. APIs pull, webhooks push. Most real integrations use both: webhooks to hear about events, APIs to act on them.
Are webhooks real time?
Effectively yes. A webhook fires as soon as the source system processes the event, typically within seconds. Polling an API on a schedule can introduce minutes of delay and wastes requests when nothing has changed.
Are webhooks secure?
They can be. Because a webhook endpoint is a public URL, you should verify every delivery: check the HMAC signature the sender includes, use HTTPS only, reject stale timestamps to prevent replays, and treat the payload as untrusted input.
Can a webhook trigger an AI agent?
Yes. This is one of the most common agent trigger patterns. A webhook from your CRM, payment provider, or ticketing system starts an agent workflow that processes the event, and platforms like Swfte Studio let you attach an agent to a webhook trigger without writing server code.