The short answer to “what is a webhook”: when an event happens in one system (a payment is captured, a parcel is delivered, an order is cancelled), that system sends an HTTP request to a URL you chose, on its own, without being asked. With a normal API you ask the question; with a webhook the news comes to you. That difference looks small, but it changes the cost, the latency and the security surface of an integration from end to end. This article explains how webhooks work, where they are used, what a correctly built webhook receiver must do, and what you need to design up front if your own system will send webhooks to others. These are the rules we follow when we build the integration layer in custom software projects.
Webhook vs API: what is the difference?
Both talk over HTTP; the difference is direction and timing. With an API you are the client: you ask “has this order been paid?” and the other side reports the current state. To learn the answer, either a user clicks a button or your system asks the same question again at an interval; that is called polling. With a webhook you hand over an address (“tell me here when a payment arrives”) and the other system sends a POST request to it when the event occurs. You do not ask; you listen. We covered the general concepts in what is API integration; this article is only about the webhook side.
Polling and webhooks are not rivals; they complement each other. When events are frequent and an instant reaction matters (payment confirmation, incoming message), a webhook is the right tool: a system that asks “is it here yet?” every five seconds wastes requests and is still, on average, two and a half seconds late. When events are rare and the other side offers no webhooks, polling is the only option; that is the case with most accounting products, which we discussed separately in accounting software integration.
How a webhook works, step by step
- You expose a public HTTPS endpoint in your system, for example /api/webhooks/payment. It accepts only POST and expects no user session.
- You register that URL in the provider’s dashboard or through its API; the provider gives you a secret key.
- When the event happens, the provider sends the event type and data as JSON to that URL, with a signature generated from the secret in a request header.
- Your server verifies the signature, stores the event, quickly returns 200 to the provider, and does the real work (marking the order paid, sending an email) separately.
- If your server does not return 200, or returns it too late, the provider resends the same event at increasing intervals, which is why the same event can arrive more than once.
Where are webhooks used?
In the business projects we see, webhooks appear most often in payment, shipping, marketplace and messaging integrations. In a payment integration, what marks an order as paid is not the success screen the user sees but the verified webhook from the provider. In a shipping integration, the carrier notifies you as the delivery status changes (out for delivery, delivered, returned) and your tracking page updates without asking. In a marketplace integration new orders and cancellations arrive this way, and with the WhatsApp Business API incoming messages and delivery reports do too. The common thread: the event happens outside your system, in someone else’s, and you do not control its timing.
The first rule of webhooks: a webhook is a notification, not the source of truth. Was the money really captured, was the order really cancelled? When in doubt, the way to verify is to query the provider’s API with the event id. A good receiver trusts webhooks but never depends on them alone.
Webhook security: signature verification and duplicate deliveries
A webhook endpoint is public by definition; anyone who knows the address can send a fake request that says “payment received”. That is why every provider signs the request with the secret, and you recompute that signature over the raw body and compare. Two classic mistakes: parsing the body to JSON first and signing the result (whitespace and field order change, so the signature never matches), and comparing with plain equality (open to timing attacks). The second big topic is duplicate deliveries: if the provider does not get a 200, it resends the same event; unless your business logic is unique per event id, stock is deducted twice or two emails go out for the same order. The Next.js route handler below applies all three rules: constant-time signature comparison over the raw body, uniqueness by event id, and pushing the heavy work to a queue so the provider gets a fast response.
// app/api/webhooks/payment/route.ts
import { createHmac, timingSafeEqual } from 'node:crypto';
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
// The signature is computed over the RAW body; do not JSON.parse first.
const raw = await req.text();
const received = req.headers.get('x-signature') ?? '';
const expected = createHmac('sha256', process.env.PAYMENT_WEBHOOK_SECRET!)
.update(raw)
.digest('hex');
// Constant-time comparison: prevents timing attacks.
if (
received.length !== expected.length ||
!timingSafeEqual(Buffer.from(received), Buffer.from(expected))
) {
return new NextResponse('invalid signature', { status: 401 });
}
const event = JSON.parse(raw) as { id: string; type: string; data: unknown };
// Idempotency: if the same event arrives again, business logic does not rerun.
const isNew = await db.webhookEvents.insertIfAbsent({
id: event.id,
type: event.type,
payload: raw,
receivedAt: new Date(),
});
if (!isNew) return NextResponse.json({ ok: true, duplicate: true });
// Heavy work (stock, email, invoice) goes to a queue; fast 200 to the provider.
await queue.enqueue('process-webhook', { eventId: event.id });
return NextResponse.json({ ok: true });
}Storing the raw event is not idle tidiness. When the queued job fails you can reprocess the record; when you and the provider disagree about whether an event was delivered, you have evidence; and when you start supporting a new field you can replay past events. We described the queue and error-tracking setup in error monitoring and logging; a webhook receiver is the best example of that article’s “silence alarm”, because a webhook that stops arriving produces no error, it just goes quiet.
What if a webhook never arrives? Retries and backfill
If your server is down for an hour, the events of that hour are not lost, but how long they are retried depends on the provider: some retry for a few hours, some for three days, some disable the endpoint entirely after five failures. So two things are set up from day one. First, the provider’s retry policy and the condition under which the endpoint is disabled are written into the handover document. Second, a periodic reconciliation job: once a night, the last 24 hours of events or transactions are pulled from the provider, compared with your records, and the gaps are processed. The webhook is the instant path; reconciliation is the seat belt. An integration built without both will one day lose data silently.
If your own system will send webhooks
If you are building a dealer portal, a SaaS product or a corporate dashboard, one day a customer will say “notify our system when an order is created”. On that day you must provide everything you expected from a receiver: events carry a unique id and a timestamp; each request is signed with a per-customer secret; deliveries go out from a queue and failed ones are retried at increasing intervals (1 min, 5 min, 30 min, 2 hours); the customer’s dashboard shows a delivery log and a “resend” button. The most overlooked point is event versioning: adding a new field to the body is fine, but renaming or changing the meaning of an existing field breaks your customers’ integrations, so the event schema is part of the contract. For public API design see REST or GraphQL, and for the wider attack surface see website security.
Five mistakes that break a webhook project
- Not verifying the signature, or saying “we will add it later”: the endpoint is public, so it runs signed from day one.
- Doing the real work inside the webhook request: when the mail server slows down the provider times out, resends the event, and the work is done twice.
- Not storing the event id: a redelivered event cannot be recognised and the same order is processed twice.
- Pointing the provider’s live webhook at a test environment: test and live keys are separate, and so should the endpoints be.
- Treating the webhook as the only source of truth: no reconciliation job, so an outage is noticed only when a customer calls.
Conclusion
A webhook is the fastest and cheapest way to connect two systems; what causes trouble is not the webhook but an incomplete receiver. Signature verification, uniqueness by event id, a queue and a reconciliation job: once those four pieces are in place, your payment, shipping, marketplace or messaging integration runs quietly for years. If you are planning a project with an integration layer, have a look at our custom software service or request a quote; during discovery we work out together which providers offer webhooks and where you will have to rely on polling and reconciliation instead.