A webhook is an automated HTTP callback: when an event occurs, one system POSTs a payload to a URL another system registered. The term — coined in 2007 as “user-defined HTTP callbacks” — names the inversion that matters: instead of your system asking repeatedly whether anything changed, the other system tells yours the moment it does. It is push built from the web’s plainest parts: an HTTPS URL, a POST, a JSON body, and a 2xx acknowledgment.
Key takeaways
| Question | Answer |
|---|---|
| The mechanism | Register a URL → event fires → provider POSTs payload → you return 2xx |
| vs. an API | APIs answer when asked; webhooks speak when something happens |
| The security bar | HMAC over the raw body, constant-time compare, timestamp window |
| The delivery truth | At-least-once, unordered, retried — dedupe and reconcile |
| The receiver’s mantra | Verify · ack fast · process async · dedupe by event ID |
The delivery, end to end
SETUP receiver exposes https://api.example.com/hooks/payments
and registers it with the provider, choosing events + a secret
EVENT charge succeeds at the provider
DELIVERY POST /hooks/payments
webhook-id: evt_8fk2 ← dedupe key
webhook-timestamp: 1767024900 ← replay guard (inside the signature)
webhook-signature: v1,d2Vio… ← HMAC-SHA256(secret, id.timestamp.body)
{ "type": "charge.succeeded", "orderId": "o-1187" }
ACK receiver verifies signature → 200 within seconds → work happens async
RETRY no 2xx? exponential backoff for hours/days → duplicates are NORMAL
Both directions of the pattern in code — a data trigger as the sender, a Cloud Function as the receiver, and the client just watching the result:
// JavaScript — Cloud Code (cloud/main.js): both directions of a webhook
// OUTGOING: any data change can notify an external system
Parse.Cloud.afterSave('Order', async (req) => {
if (req.object.get('status') !== 'paid') return;
await Parse.Cloud.httpRequest({
method: 'POST',
url: 'https://hooks.example.com/orders', // the receiver's registered URL
headers: { 'Content-Type': 'application/json' },
body: { event: 'order.paid', id: req.object.id },
});
});
// INCOMING: a Cloud Function is a ready-made webhook receiver
Parse.Cloud.define('paymentWebhook', async (req) => {
verifySignature(req.params, process.env.WEBHOOK_SECRET); // HMAC first
await markOrderPaid(req.params.orderId); // write fast, work async
return { received: true }; // 2xx before heavy processing
}); // Flutter / Dart — Back4app Flutter SDK
// The client's side of a webhook: watch its effect in real time
// (payment platform → Cloud Function receiver → database → Live Query → UI)
final orderQuery = QueryBuilder<ParseObject>(ParseObject('Order'))
..whereEqualTo('objectId', orderId);
final sub = await LiveQuery().client.subscribe(orderQuery);
sub.on(LiveQueryEvent.update, (order) {
if (order.get<String>('status') == 'paid') showReceipt();
});
// The webhook itself was handled server-side — clients just watch the data. // iOS / Swift — Back4app Swift SDK
// The client's side of a webhook: watch its effect in real time
// (payment platform → Cloud Function receiver → database → Live Query → UI)
let orderQuery = Order.query("objectId" == orderId)
let sub = try await orderQuery.subscribe()
sub.handleEvent { _, event in
if case .updated(let order) = event, order.status == "paid" {
showReceipt()
}
}
// The webhook itself was handled server-side — clients just watch the data. // Android / Kotlin — Back4app Android SDK
// The client's side of a webhook: watch its effect in real time
// (payment platform → Cloud Function receiver → database → Live Query → UI)
val orderQuery = ParseQuery.getQuery<ParseObject>("Order")
orderQuery.whereEqualTo("objectId", orderId)
val sub = ParseLiveQueryClient.Factory.getClient().subscribe(orderQuery)
sub.handleEvent(SubscriptionHandling.Event.UPDATE) { _, order ->
if (order.getString("status") == "paid") showReceipt()
}
// The webhook itself was handled server-side — clients just watch the data. Webhooks vs. APIs vs. polling
| Webhook | API call | Polling | |
|---|---|---|---|
| Initiative | Provider pushes | Consumer asks | Consumer asks on a timer |
| Timing | At the event | On demand | At the next interval |
| Wasted traffic | None at rest | None | ~98% of polls find nothing |
| Direction | One-way notify | Two-way request/response | Two-way, repeated |
| Best at | ”Tell me when" | "Do this / give me that” | Reconciliation, no-webhook providers |
The debate is largely false: mature integrations use all three — webhooks to hear about changes fast, API calls to fetch authoritative state and act, and a slow reconciliation poll as the net under the trapeze.
The receiver’s checklist
The list every provider’s docs scatter and no explainer assembles:
- Verify the HMAC on the raw body — before parsing; re-serialized JSON breaks signatures.
- Compare in constant time — string equality leaks timing; use your crypto library’s comparator.
- Enforce the timestamp window — reject deliveries older than ~5 minutes; because the timestamp is inside the signed content, an attacker can’t replay a validly signed old request with a fresh clock.
- Return 2xx fast — within seconds, before heavy work; slow handlers get timed out and retried into duplicate storms.
- Process asynchronously — enqueue, ack, then work.
- Dedupe by event ID — with a memory at least as long as the provider’s retry window.
- Don’t trust the payload for critical actions — treat the webhook as a doorbell; fetch current state from the provider’s API before shipping goods or granting access.
- Log deliveries and alert on failures — silence is indistinguishable from a broken endpoint.
One local-development note the definitional pages skip: localhost is unreachable from the internet, so development runs through a tunneling tool that lends your machine a public URL, plus a capture tool for replaying real payloads.
Delivery semantics, honestly
Webhook delivery is at-least-once: the provider retries until acknowledged, so duplicates are a feature of reliability, not a bug in it — exactly-once delivery over an unreliable network is formally impossible, and the practical equivalent is at-least-once plus your idempotent handler. Ordering is not guaranteed: retries and parallel sends interleave, so updated can arrive before created; apply events by ID and version, or refetch state. And retry windows end: an endpoint down for a weekend can permanently miss events, which is why critical money-shaped integrations pair webhooks with periodic reconciliation — the same at-least-once discipline brokers formalize, arriving over plain HTTP. That comparison generalizes: a webhook is point-to-point push to a known URL; pub/sub adds a broker, topics, and fan-out; WebSockets and SSE serve clients, not servers. Webhooks are the answer specifically when two systems that don’t share infrastructure need to hear about each other’s events.
Building the sender’s side
Emitting webhooks reliably is its own small system, and no ranking page sketches it: a queue per destination so one dead endpoint doesn’t block the rest; retries with exponential backoff and jitter; a dead-letter store with redelivery tooling after attempts run out; HMAC signing with per-endpoint secrets and rotation; event-type subscriptions so receivers opt into what they want; and delivery logs your customers can read, because “did you send it?” is the first support question. One security item unique to senders: receivers register arbitrary URLs, so validate them against internal address ranges — an attacker registering http://10.0.0.5/admin as their “webhook endpoint” is server-side request forgery wearing an integration feature.
Common use cases
- Payment lifecycles — charges, refunds, subscription changes announced to your backend as they settle.
- CI/CD triggers — the canonical git-push-starts-a-build wiring.
- Cross-app automation — form tools, chat platforms, and CRMs chained through each other’s events.
- Operational notifications — monitoring alerts and delivery updates landing in team channels.
- Data synchronization — keeping a local mirror of a partner system current without polling its whole API.
Should you use a webhook? A decision matrix
| Situation | Reach for |
|---|---|
| Another company’s system must notify yours | Webhooks — the interop default |
| Your services, your infrastructure | Pub/sub — brokered, buffered, fan-out |
| Browsers/apps need live updates | WebSockets / live queries |
| Provider offers no webhooks | Polling, politely |
| Money or access rides on the event | Webhook + verify-then-refetch + reconciliation |
| You’re the platform emitting events | Build the sender’s side above — or don’t promise reliability |
Limitations and trade-offs
- Delivery is best-effort beyond the retry window. Webhooks notify; they don’t guarantee. Reconciliation polls backstop anything that must not be missed.
- The receiver inherits uptime duty. Your endpoint’s availability now gates someone else’s events — deploys, cold starts, and timeouts all become integration bugs.
- Security is opt-in. An unverified webhook endpoint is an unauthenticated write API; the HMAC checklist is the difference between integration and injection.
- Debugging spans two companies. Delivery logs on both sides and replay tooling are what turn “it didn’t arrive” from a standoff into a diff.
- Payloads drift. Providers version event schemas; consumers pinning to exact shapes break quietly — parse defensively and ignore unknown fields.
Webhooks on Back4app
Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. Both halves of the pattern are one Cloud Code construct away, as the code tabs show. Outgoing: an afterSave trigger watching your data calls Parse.Cloud.httpRequest to any registered URL — your app becomes a webhook provider by writing the function, and the sender’s-side disciplines (retry on failure, log deliveries) live in the same file. Incoming: a Cloud Function exposed over HTTPS is a ready-made receiver — verify the HMAC against a secret in server-side config, write the result to the database, return fast — and the update then fans out to every open screen via Live Queries, completing the loop from a payment platform’s event to a user’s receipt without a server to run at either step.
Frequently asked questions
What is a webhook in simple terms?
An automated HTTP message one system sends to another the moment something happens — a doorbell instead of repeatedly checking the door. You give a provider a URL; when the event fires, it POSTs the event data there. The term dates to 2007: "user-defined HTTP callbacks."
What is the difference between a webhook and an API?
Direction and initiative. An API is request-driven — the client asks, the server answers. A webhook is event-driven — the server pushes when something happens, unasked. A webhook is really a pattern built on APIs, and most real integrations use both: webhooks to hear about changes, API calls to act on them.
What is the difference between webhooks and polling?
Polling asks on a timer and mostly hears "nothing yet" — measurement across a large automation platform famously found roughly 98% of polls return no new data. Webhooks invert it: zero requests at rest, immediate delivery on change. Polling survives as the reconciliation net under webhooks, not their rival.
What is an example of a webhook?
A payment platform POSTing to your backend when a charge succeeds; a git push triggering a CI build; a form submission appearing in a chat channel; a shipping provider announcing a delivery. Any "when X happens over there, tell my system" integration is webhook-shaped.
How do I receive a webhook?
Expose an HTTPS endpoint that accepts POST, register its URL with the provider, and select the events you want. In the handler: verify the signature, return a 2xx within seconds, and do real processing asynchronously. Test with a capture tool before wiring production logic.
Are webhooks secure?
Not by default — the endpoint is a public URL anyone can POST to. The standard defense is an HMAC signature: the provider signs each payload with a shared secret, and you recompute over the raw body and compare in constant time, rejecting stale timestamps to block replays. HTTPS always; IP allowlists as garnish.
What happens if my endpoint is down?
Good providers retry with exponential backoff, often for hours or days — which is why delivery is at-least-once and duplicates are normal. Events can still be lost past the retry window, so critical integrations reconcile with periodic API polls rather than trusting webhooks alone.
How do I handle duplicate webhook deliveries?
Dedupe on the event's unique ID: record processed IDs and skip repeats, keeping the record at least as long as the provider's retry window. At-least-once delivery plus an idempotent handler equals effectively exactly-once processing — the receiving side's half of the reliability contract.
What is the difference between a webhook and a WebSocket?
A webhook is a stateless, one-way, server-to-server HTTP notification; a WebSocket is a persistent, two-way connection, built for client-facing real-time like chat and live dashboards. Server tells server: webhook. Server streams to user interfaces: WebSocket.
Are webhooks delivered in order?
No — retries and parallel delivery reorder events freely. Handlers should apply events by ID and timestamp or, better, treat the webhook as a doorbell: fetch the object's current state from the API rather than reconstructing it from arrival order.