API integration patterns

Webhooks vs polling: which integration pattern should you use?

Use a webhook when the source can push events and seconds matter. Poll when no reliable webhook exists or a scheduled delay is acceptable. For important data, the resilient answer is often both.

By OdooMatrix Published Updated 4 min read

At a glance

A webhook means the source tells your endpoint that something happened. Polling means your integration asks on a schedule. Neither mechanism promises exactly-once delivery. The reliable unit is an idempotent state transition backed by a durable checkpoint.

1. Tell me, or I’ll ask

With a webhook, the source sends an HTTP request to your endpoint when an event occurs. With polling, your integration calls the source API and asks what changed. A webhook usually reduces delay and unnecessary requests. Polling gives the receiver control over timing and can recover state when an event was missed.

A webhook can arrive twice, late, or out of order. A poll can overlap another poll, repeat a page, skip a record because a cursor was stored too early, or hit a rate limit. “Real time” is not automatically more correct.

2. Choose from latency and recovery

Use a webhook when the provider documents the events you need and the business action should start within seconds: a paid invoice, a new lead, a shipment update, or a completed document. Your public endpoint must remain available, verify the sender, tolerate retries, and queue work quickly.

Use polling when the API has no webhook, the callback omits a required change, or a delay of minutes is acceptable. Good polling uses an updated-since cursor, pagination, stable sorting, conditional requests, and a stored high-water mark. Advance the checkpoint only after the corresponding records are durable.

For important state, let the webhook enqueue near-real-time work and run a slower poll that compares authoritative state. The poll catches a disabled endpoint, an expired subscription, a deployment outage, or an event type added after launch.

Do not keep a user’s checkout request open while several downstream systems finish. Commit the local transaction, write an outbox record with it, and process the integration asynchronously.

3. Verify, record, acknowledge, then work

Read the raw request body and verify the provider’s signature before JSON parsing changes whitespace or key order. Stripe’s webhook guide requires the raw body, the Stripe-Signature header, and the endpoint secret. Follow the provider’s scheme and official library rather than inventing a generic HMAC format.

Insert the event identifier atomically. A unique database constraint is stronger than checking memory. If the event already exists, return success without running the side effect again. Persist only the recovery data you need, and avoid storing card, identity, or other sensitive fields unnecessarily.

Return a 2xx quickly. Stripe explicitly says to acknowledge before complex logic, such as updating an invoice in an accounting system. A queue worker can perform the slower update. Stripe does not guarantee event order and advises retrieving missing objects from its API. Treat the event as a signal when the provider’s current object is authoritative.

Stripe attempts live-mode deliveries for up to three days with exponential backoff. Dashboard resend works for up to 15 days, and Stripe CLI resend works for up to 30 days. A manual resend does not cancel automatic retries. Those are Stripe rules, not universal webhook behavior. Record each provider’s retry and retention policy. Stripe’s libraries also use a default five-minute signature timestamp tolerance to reduce replay risk.

4. Poll with checkpoints and validators

Prefer a documented cursor or updated-since filter. Add overlap when timestamps are coarse, then deduplicate by stable record ID and update version. Keep the same sort order while paging. If a page fails, resume from the last committed cursor rather than from “now.”

For HTTP resources that provide validators, save the ETag and send it in If-None-Match. GitHub’s REST API best practices say an authenticated conditional request that receives 304 Not Modified does not count against the primary rate limit. GitHub also says to obey x-poll-interval and to prefer webhooks when they exist.

That rate-limit behavior is GitHub-specific, but the broader lesson is portable: use the rate-limit, retry, cursor, and cache headers the provider actually returns. Poll only as often as the business delay requires and the provider permits.

HTTP · conditional polling request
GET /resource
Authorization: Bearer …
If-None-Match: "saved-etag"

HTTP/2 304 Not Modified

5. Frequently asked questions

Are webhooks guaranteed to arrive once?

No. Providers commonly use at-least-once delivery, which means retries and duplicates. Some also deliver out of order. Make processing idempotent and reconcile important state from the source API.

How often should an API be polled?

Only as often as the business delay requires and the provider permits. Obey explicit polling and rate-limit headers. Use cursors and conditional requests so an unchanged result is cheap.

Should a webhook contain the whole record?

It depends on the provider’s contract. A thin event with an ID can reduce stale payload problems because the worker fetches current state. A snapshot can keep the historical event. Document which one is authoritative.

6. Related articles and next steps

Confirm the retry, signature, ordering, and rate-limit behavior of each provider you integrate.

Continue learning

Connecting systems?

Tell us the systems, events, acceptable delay, and how a missed update should be recovered. Email OdooMatrix to discuss the next step.

← Back to all articles