# What is a webhook? How apps notify each other

Source: https://www.plutonapps.com/resources/what-is-a-webhook

Published 2026-09-30, updated 2026-10-24, sources checked 2026-10-24. By Irfan Habib (https://www.plutonapps.com/authors/irfan-habib).

**Webhook:** A webhook is an HTTP request that one service sends to a URL in your app when something happens on its side, such as a payment succeeding or an email bouncing. Instead of your app asking repeatedly, the other service tells it. Stripe, Clerk and Resend all use webhooks.

Also known as: HTTP callback, Event notification.

How Stripe tells your app a payment went through. Simple to receive, and easy to get subtly wrong.

## What breaks when a webhook handler meets production traffic?

Its assumptions. A handler usually works in a demo, because the demo sends each event once, in order, while the server is up. Stripe's documentation says endpoints might occasionally receive the same event more than once, that delivery order is not guaranteed, and that in live mode it retries failed deliveries for up to three days.

Stripe also warns against using an event's created time to order events or spot repeats, since distinct events can share a timestamp; it says to track event ids instead. A handler that marks an order paid on the first event it sees, and ignores the rest, will eventually get it wrong.

## What does a production-ready webhook handler need to do?

A production-ready handler does four things: it verifies the signature so nobody can fake an event, ignores any event id it has already processed, answers quickly with a 2xx status while slow work runs in the background, and stores failures instead of dropping them. In order:

1. Verifies the signature, so nobody else can fake an event.
2. Records the event id and ignores one it has already processed, which is [idempotency](https://www.plutonapps.com/resources/what-is-idempotency) applied to events.
3. Answers quickly with a 2xx status and does slow work in a [background job](https://www.plutonapps.com/resources/what-is-a-background-job).
4. Stores failures rather than dropping them, and alerts someone.

Stripe is explicit about the third point: return a successful status before any complex logic that might time out, and process events through an asynchronous queue, because a spike, such as the start of the month when subscriptions renew, can overwhelm a handler that does everything inline.

> **What we see in practice** On [Inkwave](https://www.plutonapps.com/work/inkwave) every Stripe webhook is signature-checked, and failed payments are stored, not logged and dropped. A missing webhook is a missing payment nobody notices.

## How does webhook signature verification work?

The sender signs each request with a secret only it and you know, and your handler recomputes the signature before trusting the body. Stripe sends a Stripe-Signature header, signed with the endpoint's whsec_ secret using HMAC with SHA-256, and includes a timestamp so an old request replayed later can be refused.

- Verify against the raw request body: Stripe says any change to it, such as a framework parsing and re-serialising the JSON, makes verification fail.
- Its libraries reject events older than five minutes by default; never set the tolerance to zero, which turns the check off.
- Test and live mode have different secrets, and so does each endpoint.

## How do you test a Stripe webhook before going live?

Registered endpoints must be public HTTPS URLs, so for local work Stripe's CLI forwards events instead: stripe listen --forward-to localhost:4242/webhook prints a signing secret to use, and stripe trigger payment_intent.succeeded sends a test event. Our [Stripe integration guide for Lovable](https://www.plutonapps.com/resources/lovable-stripe-integration) walks through the same flow inside a Lovable project.

Webhook handling is one of the payment checks in our [production readiness check](https://www.plutonapps.com/tools/production-readiness-check).

## Frequently asked questions

### What is the difference between a webhook and an API?

With an API your app asks for data when it wants it. With a webhook the other service sends data to your app when an event happens. Instead of your app asking repeatedly whether anything changed, the other service tells it as soon as the event happens.

### Are webhooks reliable?

They are retried, not guaranteed. A handler has to cope with duplicates, events out of order and deliveries that arrive late. Stripe retries failed deliveries for up to three days in live mode, so record each event id, ignore repeats, and do slow work in a background job.

### Why does my Stripe webhook signature check fail?

Most often because the handler verifies a parsed body instead of the raw one: Stripe says any change to the raw request body makes verification fail. Other causes: the wrong signing secret, since test and live mode differ, or a server clock far enough off to break the timestamp check.

### Can I resend a webhook that my app missed?

With Stripe, yes. In the Dashboard you can click Resend on an event for up to 15 days after it was created, and the Stripe CLI can resend one for up to 30 days. Automatic retries continue regardless, so the handler must still ignore events it has already processed.

## Read next

- [The Inkwave case study: signature-checked webhooks, stored failed payments](https://www.plutonapps.com/work/inkwave)

## Sources

- [Stripe documentation: Receive events in your webhook endpoint](https://docs.stripe.com/webhooks)
- [Wikipedia: Webhook](https://en.wikipedia.org/wiki/Webhook)
- [Clerk documentation: Webhooks overview](https://clerk.com/docs/guides/development/webhooks/overview)
- [Resend documentation: Webhooks](https://resend.com/docs/webhooks/introduction)
