API Integration Patterns 2026 — Webhooks, SSE, Streaming

Photo: Jakub Zerdzicki / Pexels
Most "integration is unreliable" complaints are really pattern-mismatch complaints. A team polls for events that should be pushed, relies on webhooks for a one-time bulk pull, or reaches for WebSockets when a plain HTTP request would do. The patterns themselves are old and well documented, and so are their failure modes. This guide walks through the five that cover nearly every integration, shows what the providers' own documentation says about delivery guarantees, and lists the traps each pattern sets for the unwary.
Start with one question: who moves first, and how fast must data arrive?
Every integration answers two questions, whether or not anyone wrote them down. Who starts the conversation, your system or the source? And how stale can the data be before somebody notices? Those two answers eliminate most options before any tooling comparison begins.
Think of five ways to learn that a package has arrived. You can walk to the mailbox every hour (polling). You can install a doorbell so the courier rings it (a webhook). You can listen to a radio station that talks continuously and never listens back (server-sent events). You can stay on an open phone line where either side may speak at any moment (a WebSocket). Or you can wire an intercom between rooms in the same building, with printed forms so nobody misreads a message (gRPC).
| Pattern | Who initiates | Typical freshness | Best for |
|---|---|---|---|
| REST polling | Your system asks | Seconds to minutes, set by your schedule | Sources with no push option, low-volume data |
| Webhooks | Source sends an HTTP POST | Seconds | Events from SaaS apps and payment systems |
| Server-sent events (SSE) | Client opens, server streams | Sub-second | One-way streams to a browser or app |
| WebSockets | Either side, after a handshake | Sub-second | Chat, collaborative editing, live control |
| gRPC streaming | Either side, over typed RPC | Sub-second | Internal services with strict contracts |
The rest of this guide takes them in order of how often a team will meet them. For the no-code layer that often wraps these patterns, see the Zapier vs Make vs n8n comparison.
REST polling: the mailbox you check on a schedule
Polling means your code sends a request to an API on a timer and asks what changed. It is the oldest pattern and the one with the fewest moving parts, which is exactly why platforms still lean on it. Zapier's developer documentation notes that polling relies on list endpoints, which it calls typically the most common API endpoints for reading data from a platform, and that polling checks run every 1 to 15 minutes depending on the user's plan (Zapier Platform docs).
What polling costs
The cost is arithmetic. A day has 86,400 seconds, so a poll every 60 seconds makes 1,440 requests per day per integration, every 30 seconds makes 2,880, and every 5 seconds makes 17,280. Multiply by the number of customers or accounts you poll for and a harmless-looking schedule becomes millions of requests, almost all of which return "nothing new."
| Poll interval | Requests per day, one integration | Requests per day, 1,000 integrations |
|---|---|---|
| 15 minutes | 96 | 96,000 |
| 5 minutes | 288 | 288,000 |
| 60 seconds | 1,440 | 1,440,000 |
| 5 seconds | 17,280 | 17,280,000 |
That arithmetic is the reason to schedule by actual freshness need rather than habit. If a nightly report is read each morning, polling every minute buys nothing.
Where polling goes wrong
Three failure modes account for most polling bugs.
Missed events between polls. If two records change inside one interval and your query asks for "the latest one," you see only one. Ask for everything since your last position, page through all of it, and treat the response as a batch.
Cursor drift. A time-based cursor ("give me records updated after 10:00:00") loses rows when the source's clock skews, when a record is back-filled with an older timestamp, or when several rows share one timestamp at the page boundary. Opaque cursors that the API hands back are safer because the provider controls their meaning.
Rate-limit thrashing. Aggressive polling collides with quotas. The standard signal is HTTP 429 Too Many Requests, defined in RFC 6585 as the response for a user who has "sent too many requests in a given amount of time," and the response may carry a Retry-After header telling the client how long to wait (RFC 6585). Honor that header first, and add exponential backoff with random jitter when it is absent. Jitter matters because a thousand clients that all retry after exactly two seconds collide again at second two. The server-side view of this mechanism is covered in how rate limiting protects APIs.
Four habits that make polling dependable
- Use the API's own cursor or pagination token wherever one is offered.
- Persist the cursor after each successful batch, not before, so a crash replays the batch instead of skipping it.
- Make the consumer idempotent, because overlapping windows and retries will deliver duplicates.
- Set the schedule from the freshness requirement, then check that the quota can afford it.
Polling also has a quiet advantage. When it fails, it fails loudly and in your own logs: the request errored, the cursor did not advance, and the next run retries. Push patterns can fail in places you cannot see. A scheduled job is also not the same thing as an event-driven workflow, a distinction covered in how cron jobs differ from workflow automation.
Webhooks: the doorbell, and what happens when you are not home
A webhook inverts the direction. You register a URL, and when something happens the source sends an HTTP POST to it. Stripe describes it as pushing real-time data to your endpoint "when events happen in your Stripe account," for things like a bank confirming a payment or a recurring charge succeeding (Stripe webhooks documentation). This is the default for SaaS event integration because the latency is low and the cost is proportional to events rather than to the polling schedule.
The catch is that a doorbell only works if someone answers. Everything difficult about webhooks follows from that: the sender cannot see your side, you cannot ask it to slow down, and the delivery guarantees are whatever the provider's documentation says they are.
What providers actually guarantee
The documentation of the major platforms is more specific, and more varied, than most summaries suggest. The table below records what each page states as of October 2026.
| Provider | Response deadline | Retry behavior | Ordering and duplicates | Verification |
|---|---|---|---|---|
| Stripe | Return a 2xx before any complex logic | Live mode: up to three days with exponential backoff. Sandbox: three tries over a few hours | Order not guaranteed; same event can arrive more than once | Stripe-Signature header, HMAC-SHA256, library default tolerance of 5 minutes |
| GitHub | 2xx within 10 seconds | GitHub advises redelivering missed webhooks once your server is back up | X-GitHub-Delivery stays the same on redelivery, so it works as a dedup key |
Shared secret; optional IP allowlist via GET /meta |
| Shopify | 1-second connect timeout, 5 seconds total | 8 retries over about 4 hours | Duplicates possible; dedupe on X-Shopify-Webhook-Id |
X-Shopify-Hmac-SHA256, base64 HMAC of the raw body |
Sources: Stripe, GitHub, Shopify. Providers revise these values, so check the current page before designing around a number.
Two consequences deserve emphasis. First, the retry windows differ by orders of magnitude, from about four hours at Shopify to three days at Stripe, so "the provider retries" is not a single guarantee. Second, Shopify states that after 8 consecutive failures a subscription created through the Admin API is automatically deleted. An outage longer than the retry window does not just lose events; it can remove the subscription itself, and you may not notice until data stops arriving.
The failure modes, in the order teams meet them
Lost events during an outage. If your endpoint returns 500 for longer than the provider's retry window, the events are gone from the push channel. Some platforms keep them retrievable. Stripe's dashboard can resend an event for up to 15 days after creation and the CLI for up to 30 days, which gives you a recovery path if you know what is missing.
Out-of-order delivery. Stripe is explicit that it does not guarantee events arrive in the order they were generated, and a subscription creation can emit several events that land in any sequence. It also warns that the created timestamp has one-second resolution, so distinct events can share it. The practical rule is to treat each event as a hint to fetch current state, not as the state itself. Receive invoice.paid before invoice.created, and the handler should simply retrieve the invoice.
Duplicate delivery. Timeouts produce duplicates by design: if your server processes an event but the acknowledgment is lost, the sender retries. Stripe advises logging the event IDs you have processed and skipping repeats.
Slow acknowledgment. A handler that does the real work before replying invites timeouts, which invite retries, which add load to the endpoint that was already slow. This feedback loop is the usual root of a webhook storm.
Silent failure. A webhook that stops arriving looks identical to a quiet day. Why webhooks fail silently and how to monitor them goes through the alerting side.
A handler in the right order
The safest handler does as little as possible before replying. In pseudocode:
POST /webhook
1. read the raw body (do not parse or re-serialize it first)
2. verify the signature header against the raw body and your secret
3. check the timestamp is within tolerance (replay protection)
4. insert the event ID into a "seen" table; if it already exists, return 200
5. enqueue the payload for background processing
6. return 200
Step 1 is not cosmetic. Stripe states that signature verification needs the raw body and that any manipulation by a framework causes verification to fail. Step 3 exists because a valid payload can be captured and re-sent; Stripe puts a timestamp inside the signed content so an attacker cannot change it, and its libraries default to rejecting payloads more than five minutes old. Step 5 follows Stripe's own advice to process events through an asynchronous queue, since a spike such as the start-of-month subscription renewals can overwhelm hosts that process synchronously.
Webhook gateways and replay
Building retries, dead-letter handling, and replay yourself is real work. Gateways such as Svix and Hookdeck exist to supply it. Svix documents an eight-attempt schedule: immediately, then 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours after each failure, a total span of roughly 27.6 hours. It treats any 2xx as success and everything else, including redirects, as failure. It also disables an endpoint whose deliveries have failed for five days and offers replay of failed or never-attempted messages (Svix retries documentation).
Those numbers show what a good policy looks like: quick early retries to absorb blips, widening gaps to avoid hammering a struggling server, and a long tail that survives an overnight outage. The producer side of the same design is covered in the site's webhook design patterns guide.
Reconciliation: the net under the trapeze
No push system is a complete record. Shopify's own documentation recommends a reconciliation job that periodically fetches data you may have missed through its APIs. The pattern is simple: once a day, ask the source for everything that changed in the last 24 to 48 hours, compare it with your database, and repair the differences. For billing and payments this is not optional. It turns "webhooks are usually reliable" into "the books balance."
Server-sent events: one-way radio over plain HTTP
Server-sent events (SSE) let a server keep an HTTP response open and push messages down it as they happen. The browser side is the EventSource API. MDN describes it as a one-way connection: the server may send, and the client cannot send events back over the same channel (MDN, Using server-sent events).
How the wire format works
An SSE response uses the media type text/event-stream, UTF-8 text, and a blank line (two newlines) to separate messages. Each message can carry a data: payload, an optional event: name, an id:, and a retry: value in milliseconds that sets the reconnection delay. A line that begins with a colon is a comment, which clients ignore and servers use as a heartbeat.
The id field is what makes SSE resilient. The HTML Living Standard specifies that when the connection drops and the client reconnects, it sends a Last-Event-ID header containing the last ID it received, so the server can resume from that point. The same standard notes that proxies may drop idle connections and suggests authors send a comment line about every 15 seconds to keep them open (WHATWG HTML Standard, server-sent events). A server can also tell the client to stop reconnecting by answering with HTTP 204 No Content.
A minimal stream looks like this:
: heartbeat
id: 41
event: build
data: {"status":"running","step":"tests"}
id: 42
event: build
data: {"status":"passed"}
Why LLM APIs use it
Streaming a language model's output is the case that made SSE a household pattern. Anthropic's documentation states that setting "stream": true on a Messages request streams the response incrementally using server-sent events, and OpenAI's guide likewise describes HTTP streaming (stream=true) over server-sent events (Anthropic streaming docs, OpenAI streaming guide). The fit is natural: tokens flow in one direction, the user's next prompt is a separate ordinary request, and nothing needs a persistent two-way channel.
Where SSE bites
Connection limits. Over HTTP/1.1, browsers cap the number of open connections per browser and domain at a very low number, which MDN gives as six. Open one EventSource per widget and a few tabs will starve the page of connections. Over HTTP/2 the limit is negotiated and defaults to 100 simultaneous streams, so serving SSE over HTTP/2 removes most of the pain. The WHATWG text suggests a shared worker or a single per-page stream as workarounds.
Proxies and buffering. Intermediaries that buffer responses will hold your events until a buffer fills. Heartbeat comments help with idle timeouts but not with buffering, so test through the real proxy chain, not just on localhost.
Resumption is your job. Last-Event-ID only helps if the server assigns meaningful IDs and can replay from one. If your events are ephemeral, decide explicitly what a reconnecting client should receive.
Authentication. The native EventSource API does not let you set custom request headers, which pushes many teams toward cookies or fetch-based stream readers when a bearer token is required.
WebSockets: an open phone line
A WebSocket gives both sides a persistent, full-duplex channel. RFC 6455, published in December 2011, defines it as two-way communication over a single TCP connection that begins life as an HTTP request. The client sends a GET with Upgrade: websocket and a Sec-WebSocket-Key, and a willing server answers 101 Switching Protocols. After that, either side can send framed messages at any time (RFC 6455).
That flexibility is the point and the cost. Use it when both directions carry real-time traffic: chat, multiplayer state, collaborative cursors, live control of a device. If the client only ever listens, SSE gives you reconnection and plain-HTTP infrastructure for free.
The operational bill
State lives in connections. Every connection is stateful, so scaling past one server means routing a user back to the same machine, or moving session state and message fan-out into a shared store.
Liveness needs a protocol. RFC 6455 defines Ping and Pong control frames for this; a receiver of a Ping must reply with a Pong echoing the same data, and an unsolicited Pong works as a one-way heartbeat. Dead connections (a phone that walked out of range) otherwise look alive for a long time.
Origin checks are mandatory thinking. Browsers always send an Origin header on the handshake, and the RFC says a server that does not want the connection can refuse it, for example with a 403. Skipping this check can let a malicious page in another origin open authenticated sockets from a victim's browser.
Reconnection is on the client. Unlike EventSource, the browser WebSocket API does not reconnect for you. Clients need backoff, message queuing while offline, and a way to resync after rejoining.
Backpressure. A slow client lets outbound messages pile up in server memory. Decide in advance whether to drop, coalesce, or disconnect.
Edge platforms add their own constraints on long-lived connections; the edge runtimes 2026 guide covers the platform-specific limits. Many teams hand the whole layer to a managed service such as Ably, Pusher, or Liveblocks, or use libraries such as Socket.IO that add reconnection logic on top. For a small team, paying a vendor for connection management is usually cheaper than staffing it.
gRPC and streaming RPC: typed contracts between your own services
gRPC is a remote-procedure-call framework. Instead of designing URLs and JSON, you describe services and messages in a Protocol Buffers file, and tools generate client and server code in many languages. By default gRPC uses Protocol Buffers as both its interface definition language and its message format, though other formats such as JSON are possible (gRPC introduction).
Its streaming support is first-class. The gRPC core concepts page lists four kinds of service method: unary (one request, one response), server streaming, client streaming, and bidirectional streaming where both sides send sequences of messages on independent streams. It also builds in deadlines: a client states how long it will wait, and the call ends with a DEADLINE_EXCEEDED error when time runs out (gRPC core concepts). Propagating a deadline through a chain of services is one of the more valuable reliability features in the whole list, because it stops work nobody is waiting for.
Why it stays inside the building
gRPC shines for internal, high-throughput service-to-service traffic where a shared schema prevents drift. It is awkward at the public boundary. Browsers cannot speak native gRPC directly, and gRPC-Web, the JavaScript implementation for browser clients, needs a proxy (Envoy is the default) or a server with built-in support. Its project page lists unary and server-streaming calls as supported and says client-side and bidirectional streaming are "not currently supported" (grpc-web repository). Debugging is also harder than with REST, since binary payloads cannot be read with a plain curl.
That is why most public APIs ship REST, sometimes alongside GraphQL, and keep gRPC behind the firewall. If you are choosing between REST, GraphQL, and typed RPC for an API you will expose, the GraphQL vs REST vs tRPC comparison covers that decision.
Choosing: a decision path you can actually follow
Work through these in order and stop at the first that applies.
1. Is this between services you control, with a shared schema?
YES -> gRPC if throughput or streaming matters; plain REST if it does not.
NO -> continue.
2. Does the source offer webhooks?
YES -> Webhooks, plus a scheduled reconciliation poll.
NO -> continue.
3. Must the data arrive faster than your polling interval can afford?
YES, server -> client only -> SSE.
YES, both directions -> WebSockets or a managed realtime service.
NO -> Polling with a cursor and a cadence set by need.
4. Are you streaming model output to a user?
-> SSE.
A few combinations come up constantly. Payments and billing want webhooks as the fast path and daily reconciliation as the correctness path. Dashboards want SSE for the live tail and a plain REST call for the initial load. Warehouse loading from a large transactional database often outgrows polling and moves to change data capture. Debezium is the best-known open-source example: its MySQL connector reads the server's binlog and writes each row-level change as an event to a Kafka topic, and because the connector stores its position, it resumes after a restart without missing changes made during an outage, provided the database has not yet purged the binlog entries it needs (Debezium tutorial).
For the broader mechanics of reacting to events inside automation tools, see how event-driven automation works. For the beginner-friendly vocabulary behind webhooks and endpoints, API basics for no-code automation is a gentler start, and integrating tools without coding shows these patterns hidden behind visual builders.
Reliability rules that apply to every pattern
Idempotency first. An operation is idempotent when repeating it has the same effect as doing it once. RFC 9110 defines it that way and classes PUT, DELETE, and the safe methods such as GET as idempotent, which is why a client may automatically retry them after a connection failure (RFC 9110). POST is not idempotent by definition, so integrations built on it need help. Stripe's approach is representative: the client sends an Idempotency-Key header, the server saves the status code and body of the first request made with that key, and retries with the same key return the saved response. Keys can be up to 255 characters, Stripe suggests random V4 UUIDs, and for API v1 keys may be pruned after at least 24 hours (Stripe idempotent requests). An IETF working-group draft proposes standardizing the header; the latest version visible at the time of writing is an Internet-Draft, not a finished standard. The consumer-side version of this discipline is explained in how idempotency keys make automation safe.
Dead-letter handling. When a message fails repeatedly, move it to a queue where a person can inspect it instead of retrying forever or dropping it. Why automation needs error queues makes the case in detail.
Observability. Log every integration interaction with an ID that connects the inbound event to the downstream effects. OpenTelemetry instrumentation is a common vendor-neutral way to carry that context between services.
Reconciliation. Compare full state on a schedule. This is the only defense against the failures nobody thought to alert on.
Versioning. Providers change payload shapes. Stripe's documentation notes that an event is structured by the API version set when the event occurred, so an old account version keeps producing old-shaped events until you change it. Pin versions, read deprecation notices, and test upgrades in a sandbox first.
Security. Treat a webhook URL as a credential. Verify signatures, restrict to the provider's published IP addresses where they are offered, rotate signing secrets (Stripe allows the previous secret to stay valid for up to 24 hours during a roll), and never put API keys in a payload URL, as GitHub's guidance warns.
What each pattern gets wrong when misused
| Misuse | Symptom | Better fit |
|---|---|---|
| Polling every second for events | Quota exhaustion, 429 responses, wasted compute | Webhooks or SSE |
| Webhooks as the only record of truth | Gaps after outages, undetected | Webhooks plus reconciliation |
| WebSockets for a request-response feature | Needless connection state, harder scaling | Plain HTTP |
| SSE for two-way chat | Awkward second channel for client messages | WebSockets |
| gRPC exposed directly to browsers | Proxy layer, limited streaming | REST or GraphQL at the edge |
| Parsing the webhook body before verifying | Signature checks fail intermittently | Verify against the raw body first |
Questions people ask
Q: Should I use webhooks or polling? A: Use webhooks when the source offers them and you need seconds-level freshness, and keep a scheduled poll as a safety net. Use polling when the source has no push option or when updates are infrequent enough that a schedule is cheaper than building an endpoint. Zapier, for example, documents polling triggers that run every 1 to 15 minutes depending on plan.
Q: How fast must a webhook endpoint respond? A: It depends on the sender. GitHub documents 2xx within 10 seconds, Shopify allows 5 seconds in total, and Stripe says to return a 2xx before any complex logic. A safe design acknowledges within a second or two and does the work from a queue.
Q: What is the difference between SSE and WebSockets?
A: SSE is a one-way stream from server to client over ordinary HTTP, with built-in reconnection through Last-Event-ID. A WebSocket is a two-way channel that starts as an HTTP upgrade and then speaks its own framed protocol. Choose SSE when only the server talks, and WebSockets when both sides do.
Q: How do I stop processing the same webhook twice?
A: Record the delivery or event ID in a table with a unique constraint before doing the work, and skip any event whose ID already exists. Stripe recommends logging processed event IDs, GitHub keeps X-GitHub-Delivery identical on redelivery, and Shopify provides X-Shopify-Webhook-Id for the purpose.
Q: How do I test webhooks on my laptop?
A: Use a tunneling tool such as ngrok to expose a temporary public HTTPS URL, or use a provider's CLI forwarder; Stripe's CLI can forward events to a local port with stripe listen. Replay real sample events from the provider's dashboard, and keep signature verification switched on in tests so the code path matches production.
Q: When should I move from polling to change data capture? A: When polling a large transactional database becomes expensive or misses intermediate states. Log-based tools such as Debezium read the database's own transaction log instead of querying tables, which captures every committed change in order and resumes from a stored position after outages. The trade-off is operating a streaming platform such as Kafka.
Q: Where do AI agents fit? A: Agents typically call REST APIs for actions, receive webhooks for events, and stream model output to users over SSE. The reliability rules above apply unchanged, and arguably matter more because an agent can repeat a call without anyone noticing. The wider question is covered in AI agents vs traditional automation.
The takeaway
The five patterns are not rivals so much as tools with different assumptions about who speaks first and who is listening when something goes wrong. Polling assumes you can afford to ask repeatedly. Webhooks assume your endpoint is up and the provider's retry window is longer than your outage, which the documentation shows can be anywhere from four hours to three days. SSE assumes one-way flow, WebSockets assume you can manage state per connection, and gRPC assumes both ends share a schema.
The durable habit is to design for the failure instead of the demo. Make handlers idempotent, acknowledge fast and work from a queue, keep a reconciliation poll under every push channel, and alert on silence as well as on errors. Do that and the choice of pattern becomes a performance decision rather than a reliability gamble.
Read next:
Follow new articles
Add SciHub101 to your feed reader to see new science and technology stories.
About the Author
SciHub101 is an independent science and technology publication founded and edited by Buğra Sözeri, a software engineer and app developer based in Türkiye. AI tools assist with drafting and research. Source documentation in the existing archive is uneven, and articles are being reviewed and corrected. The Editorial Team byline identifies editorial responsibility; it does not certify specialist or independent human review. Where we compare products, anything built by our own team is labelled as such — see the editorial guidelines and About page for how corrections and disclosures work.
Related Articles

Google Sheets Automation Guide — Apps Script & Zapier 2026
Automate Google Sheets with built-in tools, Apps Script, Zapier, AppSheet, and AI calls. Working code, real quotas, and the limits that break spreadsheets.

Document Automation 2026 — DocuSign, PandaDoc, Dropbox
Compare DocuSign, PandaDoc, Dropbox Sign, and AI-first tools for e-signature, contract generation, and approvals, with listed prices and legal basics.

Webhook Design Patterns 2026 — Reliability and Security
Webhook design patterns for reliable, secure integrations: signature verification, idempotency, retries, queues, and dead-letter handling. Updated for 2026.

How to Integrate Tools Without Coding — No-Code Guide
Connect your favorite apps: Gmail, Slack, Airtable, Salesforce. No API knowledge required. Zapier, Make, and native integrations compared.

API Basics for No-Code Automation — Webhooks Explained
APIs and webhooks explained for non-technical users. GET vs POST, authentication, real Stripe webhook example. Build advanced automations without coding.
