A webhook is a reverse API call: instead of your software asking a server for data, the server pushes data to a URL you registered the moment an event happens. An API is the broader contract that lets two systems request and exchange data on demand, and webhooks are typically delivered through that same API layer. The key condition is direction and timing. If you need fresh data the instant something changes, you want a webhook. If you need to ask a question and get an answer whenever you choose, you want an API call.

Pull model (API): your code sends a request, waits, and receives a response. You control the clock. Push model (webhook): the provider sends an HTTP POST to your endpoint after a trigger. Their clock drives yours. Not either-or: most mature platforms ship both, and production systems usually combine them.

The Core Difference Comes Down to Who Starts the Conversation

Think of an API as a library desk. You walk up, ask for a specific book, and the librarian hands it over. You decide when to visit and what to request. A webhook is the library calling you because the book you reserved just arrived. You didn't ask at that moment; the event triggered the call.

That direction change has real engineering consequences. In a pull model, your system decides the polling frequency, which caps your worst-case latency at the interval you choose. In a push model, the provider decides when to fire, and your endpoint must be reachable, accept the request fast, and process it reliably. The receiving side owns uptime in a way it doesn't in a pull-only design.

Both mechanisms usually travel over HTTPS and carry structured payloads, often JSON. A webhook is technically an HTTP request, which is why people conflate the two terms. The distinction isn't the transport; it's the trigger logic and who initiates.

The boundary matters most during failure. If your API call fails, you see the error immediately and retry in your own code. If your webhook endpoint is down, the provider may retry on its own schedule or may drop the event after a limit you don't control. That asymmetry is where most integration bugs are born.

Plain-English Definitions With Three Realistic Examples

A webhook is an event notification delivered to a URL you control. You register the endpoint, choose which events you care about, and the provider posts a payload when one fires. Stripe's documentation describes webhooks as "events that happen in your account" sent to your endpoint, and its event types include payment_intent.succeeded and charge.refunded. That specificity matters: you subscribe to named events, not a vague firehose.

An API is a defined set of endpoints, methods, and authentication rules that lets your code read or write data. GitHub's REST API, for instance, exposes endpoints for repositories, issues, and pull requests, each returning structured JSON when you call it with a token. You choose the endpoint, the moment, and the parameters.

Three concrete cases show the split. First, a payment processor: Stripe pushes payment_intent.succeeded to your endpoint so you can provision an account within seconds, while you call its API to issue a refund on demand. Second, a code host: GitHub sends a webhook when a pull request opens, and you call the API to fetch the full diff. Third, a chat platform: Slack delivers event callbacks to your app when a message is posted, and you call its Web API to post a reply. In each pair, the push handles "something happened" and the pull handles "give me the details now."

Notice the pattern. The webhook tells you that something changed; the API lets you ask what changed in full. Many integrations use one to trigger the other.

What Each Model Costs You in Latency and Load

Polling has a computable price. Suppose you check an endpoint every 60 seconds across 10,000 tracked records. That's 14,400 requests per record per day, or 144 million requests daily before you've done any real work. Providers commonly meter API calls against rate limits, and GitHub's REST API documentation states a primary rate limit of 5,000 requests per hour for authenticated users. At that ceiling, aggressive polling burns your budget fast.

Webhooks flip the equation. One event produces one delivery, so the request count scales with activity rather than time. A quiet system sends nothing. A busy one sends exactly as much as it needs to. For sparse but urgent events, that's a large efficiency win.

The trade is control. Your worst-case latency under polling equals your interval plus processing time. Poll every five minutes and a critical event can sit unnoticed for nearly five. Webhooks typically arrive in seconds, but the provider's retry policy governs what happens when delivery fails. Stripe's documentation describes automatic retries with exponential backoff for failed deliveries, which smooths transient outages but doesn't guarantee instant recovery.

Pick by event density. High-frequency, latency-tolerant sync jobs often poll cleanly. Low-frequency, latency-sensitive triggers favor webhooks, with a periodic reconciliation poll as a safety net.

Dimension API (pull) Webhook (push)
Who initiates Your client The provider
Typical latency Your polling interval Seconds, provider-dependent
Request volume Fixed by interval Γ— records Scales with events
Failure visibility Immediate, in your code Deferred, via provider retries
Endpoint requirement Outbound only Publicly reachable HTTPS URL
Best fit On-demand reads, bulk sync Real-time triggers, sparse events

How to Set Up a Webhook Endpoint Without Losing Events

Follow these steps in order. Each one addresses a failure mode that bites teams in production, not just in tutorials.

1: Create a dedicated HTTPS endpoint. Give it its own route, such as /webhooks/stripe, separate from your user-facing API. Confirm your TLS certificate is valid; providers reject invalid certs. 2: Verify the signature before parsing. Stripe signs payloads and documents verification with its Stripe-Signature header and a shared signing secret. Compute the expected signature over the raw request body, compare in constant time, and reject mismatches with a 400. 3: Read the raw body first. Many frameworks parse JSON before your handler sees it, which breaks signature checks. Capture the raw bytes, verify, then deserialize. 4: Store the event ID and deduplicate. Providers retry, so the same event can arrive twice. Keep a table of processed event IDs and short-circuit duplicates. 5: Acknowledge fast, process async. Return a 2xx within the provider's timeout window, then hand the payload to a queue. Stripe's docs note that slow responses can cause timeouts and retries. 6: Log the full payload with headers. When a delivery misbehaves, the raw record is your only evidence. 7: Run a reconciliation poll. Schedule a daily API pull to catch events lost during outages that exceeded the retry window.

Failure handling deserves its own paragraph. If signature verification fails, return 400 and alert; never process unverified payloads. If your queue is down, return 5xx so the provider retries rather than accepting and dropping. If you see the same event ID twice, log it and skip. And if retries exhaust, your reconciliation job is the last line of defense.

Webhook Security Risks the Pull Model Avoids

A public endpoint is an attack surface. Anyone who learns your URL can POST junk to it, which is why signature verification isn't optional. Without it, an attacker can forge a "payment succeeded" event and trigger fulfillment you never got paid for. Stripe's verification docs exist precisely because this attack is common.

Replay attacks are the subtler cousin. A captured, valid payload can be resent later. Timestamp checks and event-ID deduplication close that gap; most providers include a timestamp in the signature scheme for this reason. If your handler ignores timestamps, a week-old refund event can fire twice.

Server-side request forgery cuts the other way. If your own systems let users supply webhook URLs, an attacker can point one at your internal network. Restrict outbound delivery targets to public addresses and block private IP ranges.

The pull model avoids these because your client initiates every exchange over authenticated channels you control. That's a genuine security advantage, not a marketing point. Use webhooks where latency justifies the exposure, and harden the endpoint accordingly.

When a Webhook Beats an API Call, and Vice Versa

Choose a webhook when the event is sparse, urgent, and the provider supports it natively. Payment confirmations, CI build completions, chat messages, and deployment status changes all fit. You want the notification within seconds and you don't want to poll a quiet endpoint thousands of times hoping to catch it.

Choose an API call when you need data on demand, when you're reading bulk records, or when the provider offers no webhook for that event. Report generation, historical queries, and user-triggered lookups are pull-shaped by nature. If a user clicks "refresh," that's an API call, not a webhook.

Choose both when correctness is non-negotiable. Payments, order fulfillment, and anything with money attached deserve a webhook for speed and a scheduled API reconciliation for truth. The webhook gets you there fast; the poll proves you didn't miss anything.

Skip webhooks when you can't expose a stable public endpoint, when your team can't operate a queue and dedup store, or when event volume is so high that per-event processing costs more than a batched poll. A startup without production monitoring should think hard before taking on webhook uptime.

Signature Verification and Idempotency in Practice

Two properties separate a hobby webhook handler from a production one: authenticity and exactly-once effect. Signature verification establishes the first. Idempotency establishes the second.

Authenticity means proving the payload came from the provider and wasn't altered. The pattern is consistent across platforms: the provider hashes the raw body with a secret only you two share, and sends the result in a header. Your handler recomputes and compares. Any mismatch, missing header, or stale timestamp means reject.

Idempotency means processing the same event twice produces the same result as processing it once. The mechanism is a unique key. Store the provider's event ID in a database with a unique constraint, and let the insert fail on duplicates. If the insert succeeds, process; if it fails, skip.

Combine them and you get a handler that's safe against forgery and safe against retries. Neither is hard individually; the discipline is doing both on every route, including the ones added in a hurry.

One more practical rule: verify against the raw bytes, not a re-serialized object. JSON key ordering and whitespace can change between parse and re-encode, and the signature won't match. Capture the body as a string before any framework touches it.

Common Misuse: Treating a Webhook as a Database

The most frequent mistake is assuming the webhook payload contains everything you need. It often doesn't. Many providers send a thin event with an ID and a type, expecting you to call the API for the full record. If you build fulfillment logic on the payload alone, you'll hit missing fields at the worst moment.

The correct pattern: the webhook says what happened, and the API call fetches the current state. Stripe's own guidance recommends retrieving the object via the API after receiving an event, partly because events can arrive out of order. Out-of-order delivery is normal in distributed systems, and the API's current-state read resolves it.

A second misuse is using a webhook as a message queue. Endpoints time out, so long processing inside the handler invites retries and duplicate work. Acknowledge, enqueue, process elsewhere.

A third is skipping the reconciliation poll because "webhooks are reliable." They're reliable within the provider's retry window, which is finite. Anything that must be exactly right needs a periodic API sweep to close the gap. Treat webhooks as a fast path and the API as the source of truth.

How the Two Models Combine in Real Systems

Production integrations rarely pick one. A typical e-commerce flow uses a webhook to learn that payment succeeded, an API call to fetch the full order and customer record, a queue to process fulfillment, and a nightly API reconciliation to catch anything the webhook missed. Each layer does what it's best at.

Event-driven architectures formalize this. A queue or stream sits between the webhook receiver and the business logic, so the receiver's only job is verify-and-enqueue. That decoupling means a downstream outage doesn't cause webhook timeouts, and bursty event volume doesn't overwhelm the handler.

Observability ties it together. Track delivery success rates, signature failures, duplicate counts, and reconciliation discrepancies. If the reconciliation poll routinely finds events the webhook missed, your endpoint has a problem worth fixing before it becomes a customer-visible bug.

The mental model to carry: webhooks optimize for latency, APIs optimize for control and completeness. Systems that need both properties run both, and the reconciliation job is what makes the combination trustworthy.

FAQ

Do I always need both a webhook and an API?

No. If your only need is on-demand lookups with no real-time requirement, an API alone is simpler and avoids endpoint uptime obligations. Add webhooks when latency matters and the provider exposes the events you need. Add reconciliation polls when correctness is critical.

Can a webhook fire before my system finishes processing the previous one?

Yes, and that's normal. Providers deliver events independently, and ordering isn't guaranteed across all platforms. Use event timestamps and the API's current-state read to resolve conflicts, and make your handler idempotent so concurrent duplicates don't double-process.

What happens if my endpoint is down when a webhook fires?

It depends on the provider's retry policy. Many retry with exponential backoff for a bounded window, then stop. Events sent during a long outage can be lost permanently, which is why a scheduled API reconciliation poll is the standard safety net for money-critical flows.

Is a webhook just an API call in reverse?

Mechanically, yes: it's an HTTP request to a URL you registered. The difference is who initiates and what triggers it. An API call is client-initiated and on-demand; a webhook is provider-initiated and event-driven, with different failure and security implications.

How much does polling cost compared to webhooks at scale?

Polling cost scales with time, not events. Checking 10,000 records every minute is 14.4 million requests per day regardless of activity, which can collide with rate limits like GitHub's documented 5,000 requests per hour for authenticated users. Webhook volume scales with actual events, so quiet systems cost almost nothing.

Which is more secure, an API or a webhook?

Neither is inherently safer; the risks differ. APIs rely on authenticated outbound calls you control. Webhooks expose a public endpoint to forgery and replay, which signature verification and timestamp checks mitigate. Both require TLS and careful secret handling.

Why does my webhook handler see duplicate events?

Retries are the usual cause. If your endpoint responds slowly, times out, or returns a 5xx, the provider resends. Duplicates are expected behavior, not a bug, and the fix is event-ID deduplication plus fast acknowledgment before async processing.

Can I use webhooks without a public server?

Not for receiving them. A webhook needs a reachable HTTPS URL, so local development requires a tunneling tool or a hosted relay. If exposing an endpoint isn't feasible, polling the API is the practical alternative until your infrastructure supports inbound requests.