Event-Driven APIs: Webhooks, WebSockets, and SSE
An event-driven API pushes data to a client the moment something happens on the server, instead of waiting for the client to ask. A REST API only speaks when spoken to. The client sends a request, the server sends one response, and the exchange ends. An event-driven API flips that. The server holds a connection open, or reaches out on its own, and sends a message the instant an order ships, a price changes, or a model finishes generating the next token of a chat response.
That last example is not incidental. When you use a chat product and watch the reply appear word by word, you are looking at an event-driven API in production. The server is pushing tokens to your browser as the model produces them, rather than making you poll for a finished answer. The same underlying idea, an update the moment there is something to say, shows up everywhere from payment confirmations to live dashboards to that streaming chat response.
What Is an Event-Driven API?
An event-driven API is an API that delivers data to a consumer in response to an event, rather than in response to a request. The server or a message broker sends the update as soon as the underlying event occurs, and the consumer does not need to ask again to find out.
This is the opposite of the request-response model that REST APIs use. A REST client that wants to know whether an order has shipped has to keep asking, an approach usually called polling. An event-driven API removes the asking. The client (or another service) expresses interest once, and the update arrives on its own schedule, driven by the event, not the clock.
Three patterns account for most production event-driven APIs, and each solves the delivery problem differently. Webhooks send a one-off HTTP request when something happens, WebSockets hold open a two-way connection for continuous back-and-forth, and server-sent events (SSE) hold open a one-way stream from server to client. The rest of this guide works through each one, when to reach for it, and how a gateway that already governs your REST traffic can govern this traffic too.
Event-Driven vs. Request-Response: The Core Difference
The distinction comes down to who decides when communication happens.
- Trigger: request-response is client-initiated (the client always asks first). Event-driven is server-initiated (the server decides when there is something worth sending).
- Interaction pattern: REST is a discrete request/response exchange, even when the underlying HTTP connection itself is reused or multiplexed across many requests. Event-driven patterns replace that per-request exchange with an open stream, or with a callback notification the provider sends on its own schedule.
- Coupling: REST couples the client to knowing when to ask. Event-driven APIs couple the client only to knowing how to receive, which is why they scale better under unpredictable update frequency.
- Cost under uncertainty: if you do not know when the next update will land, polling means guessing an interval and living with either wasted requests or stale data. An event-driven API removes that guess entirely.
Neither model is strictly better. Request-response is simpler to build, test, and cache, and it is the right default for anything a client asks for on demand: fetching a user profile, submitting a form, running a search. Event-driven patterns earn their added complexity when the update frequency is unpredictable and the client genuinely needs to know close to the moment it happens.
The two also are not mutually exclusive within one API. A REST resource can let a client create a subscription, and the updates for that subscription can then arrive as webhook callbacks or over a stream. What matters is which model fits a given operation, not which one an entire API is declared to belong to.
Three Ways to Build Event-Driven APIs
Webhooks
A webhook is a single HTTP request that one server sends to another when a specific event occurs. There is no persistent connection: the consumer registers a callback URL with the provider in advance, the provider sends one request to that URL when the event fires, and the connection closes as soon as the request completes.
Nothing about that exchange guarantees delivery on its own. Reliability has to be built, not assumed. A provider that wants dependable delivery needs durable storage for events, a retry policy with backoff for when the receiver is unreachable, and a way to tell whether the receiver actually acknowledged the request. The receiver carries its own half of the work. Retries mean the same event can arrive more than once, and delivery order is not guaranteed, so a consumer needs to deduplicate by event ID rather than assume every request is new and in sequence. It should also verify that the request genuinely came from the provider, typically by checking a signature over the payload and rejecting anything outside a short timestamp window, to guard against replay. Stripe's webhook documentation is a concrete, widely referenced example of all of this: retries for up to three days, no guarantee that events arrive in the order they were generated, and a signed payload the receiver is expected to verify before trusting it.
That discrete, trackable shape, one request per event rather than an open stream, is exactly why webhooks fit server-to-server notifications despite the extra engineering. Examples include a payment provider telling your backend that a charge cleared, a CI system telling a chat integration that a build finished, or a CRM telling your data warehouse that a record changed. None of these need a live connection, and all of them are easier to retry and audit as discrete requests than as messages down a stream that was open at the time.
WebSockets
A WebSocket begins as a normal HTTP request that asks the server to upgrade the connection, standardized in RFC 6455. Once the server agrees, the same TCP connection switches protocols and stays open, and either side can send a message at any moment without opening a new connection first. That full-duplex, always-open property is what makes a WebSocket API the right fit when both sides genuinely need to talk: a navigation app streaming location updates to a server while the server streams directions back, or a multiplayer app where every client both sends and receives continuously.
The cost of that flexibility is operational. A WebSocket connection has to be kept alive, reconnected on drops, and often needs its own proxy configuration, because plain HTTP infrastructure was not built with long-lived duplex connections in mind.
Server-Sent Events (SSE)
SSE is a plain HTTP response that never finishes. The server keeps the connection open and streams a sequence of text events, formatted per the text/event-stream specification, for as long as it has something to send. Communication only flows one way, server to client. The browser's built-in EventSource client reconnects automatically if the connection drops, sending a Last-Event-ID header so the server can pick up where it left off, but a client built on raw fetch streaming instead of EventSource has to implement that reconnect logic itself. Either way, resuming from the last event depends on the server retaining enough history to replay it. The transport does not guarantee that on its own.
That one-way, HTTP-native shape is exactly the traffic pattern of streaming a chat completion. The model produces tokens and the client only needs to display them as they arrive, never sending anything back mid-response. It is also why most major LLM providers, including OpenAI and Anthropic, stream their completions over SSE rather than a WebSocket. When the only thing that needs to move is server-to-client, SSE gets the job done over infrastructure that already understands plain HTTP, with none of the connection-upgrade overhead a WebSocket carries.
Comparing the Three
| Webhook | WebSocket | SSE | |
|---|---|---|---|
| Direction | Server to server, one request per event | Bidirectional | Server to client, one-way |
| Connection lifetime | None. Closes after each delivery | Persistent, open until closed | Persistent, open until closed |
| Reliability model | Retries, dedup, and acknowledgment, if the integration implements them | Application-defined | Application-defined |
| Typical consumer | Another backend or third-party system | An interactive client that also sends data | A client that only needs to receive |
| Transport | Standard HTTP request | Upgraded TCP connection | Standard HTTP response |

Choosing the Right Pattern: A Decision Framework
Start from what the consumer actually needs to do, not from which pattern sounds most modern.
- The consumer is another system, and you need to know whether delivery succeeded. Reach for webhooks. The retry-and-log behavior is the point, not a bonus feature.
- The interaction itself is bidirectional and latency-sensitive in both directions. Reach for a WebSocket, and reserve it for cases like chat or multiplayer where client and server are both sending continuously as part of the same exchange. A client that only occasionally sends something, like one message while otherwise watching a stream, usually fits fine as a regular request alongside an SSE stream. Occasional client-to-server traffic by itself does not require a WebSocket.
- The consumer only needs to receive a live stream, and it is a browser or app you control. Reach for SSE. You get a persistent push without paying for a bidirectional connection you never use.
- You genuinely do not know the update frequency and traffic is low. Plain polling, with reasonable caching, is still a legitimate answer. Event-driven patterns solve a real cost problem, but they are not free of their own complexity, and a low-traffic internal tool checking a status field once a minute rarely needs any of the three.
The decision rarely turns on raw performance. It turns on who initiates, whether the receiver needs a guarantee that the message landed, and whether the client has anything of its own to say back.
Event-Driven API Architecture: Brokers, Schemas, and AsyncAPI
Once you have more than one producer or consumer, most event-driven systems introduce a message broker: a piece of infrastructure that receives messages and routes them to whoever has expressed interest, often storing them until delivery so a temporarily offline consumer does not lose events. Kafka, RabbitMQ, and Solace are common examples.
Around the broker, three roles recur. A producer (or publisher) sends messages to the broker. A consumer (or subscriber) connects to the broker, declares interest in a type of message, and receives it as it arrives. A channel (sometimes called a topic or routing key) groups related messages so a consumer can subscribe to only what it needs rather than everything the broker carries.
Messages carried this way still need a contract, the same way a REST endpoint needs an OpenAPI definition. That is what the AsyncAPI specification is for. It is an open, vendor-neutral format for documenting channels, message schemas, and the brokers or protocols behind them, playing roughly the role for event-driven APIs that OpenAPI plays for REST. A schema defines what a given message must contain. Schema evolution is the practice of changing that schema over time, adding an optional field or a new message version, without breaking consumers still running the previous version.
Common Challenges
Event-driven APIs trade one set of problems for another, and it is worth naming the trade honestly rather than presenting push-based delivery as a strict upgrade.
- Schema evolution: A producer that adds a required field or renames one can break every consumer still expecting the old shape. Versioning the schema and keeping new fields optional for at least one deprecation window avoids most of this.
- Delivery guarantees: Deciding whether a message can arrive more than once, whether it can arrive out of order, and what a consumer should do about both, has to be an explicit design decision. Different brokers and protocols make different trade-offs here, and no single choice is right for every payload.
- Observability: A request-response call is easy to trace. One request, one response, one log line. A push-based flow that fans out across a broker to several consumers is harder to follow end to end, and teams that skip this early tend to regret it once something goes missing in production.
- Fault tolerance: A consumer that is down when an event fires needs a plan: does the broker retain the message and redeliver it, or is it lost? Webhooks solve this with sender-side retries. Broker-based systems solve it with message retention. A raw WebSocket or SSE stream generally does not solve it at all, since anything sent while the client was disconnected is simply gone unless the application layer adds its own catch-up mechanism.
How an API Gateway Supports Event-Driven Patterns
Adding webhooks, WebSockets, or SSE to your API surface does not remove the governance problem that already exists for REST traffic. It just moves the same problem onto a different transport. A WebSocket endpoint still needs authentication before a client can open it. A webhook receiver is still an HTTP endpoint that needs rate limiting to survive a retry storm. An SSE stream still needs the same access logging you already expect from every other API. Teams that treat these as "just infrastructure" rather than as APIs tend to end up with exactly the endpoints nobody applied a policy to.
It helps to separate two traffic directions that are easy to blur together, because they are governed by different parts of a gateway and never hand off to one another. Inbound traffic is a client or another service calling into your APIs, whatever the transport: a REST call, a WebSocket connection, or a webhook delivery you are receiving. Outbound traffic is your own application calling out to something else, most notably an LLM provider. These are two separate paths, not two steps in one pipeline, even though a single feature often triggers both. A chat UI makes an inbound call into your backend, and your backend then makes its own outbound call to a model. A gateway that conflates the two ends up applying the wrong policy to the wrong traffic.
WSO2 API Platform splits governance along that same line. Its API Gateway handles REST, GraphQL, and WebSocket traffic under one authentication and rate-limiting layer, as described in WSO2's API Platform documentation, while broker-backed event traffic over WebSub, WebSocket, and SSE goes through a dedicated Event Gateway described below. None of those get a separate, lesser-governed lane. A WebSocket or SSE endpoint goes through the same control plane as a REST API published alongside it. For the specific case this article is about, bridging that traffic to a message broker, the platform also ships a dedicated Event Gateway: a purpose-built runtime, documented separately from the general-purpose API Gateway, that mediates between web-friendly protocols (WebSocket, WebHook, SSE) and brokers such as Kafka, MQTT, and Solace, enforcing authentication, rate limiting, and delivery guarantees on that bridge through its own policy engine. Its webhook support follows the W3C WebSub specification: a provider pushes events to one endpoint while subscribers manage their subscription through a separate one, so the publish/subscribe relationship itself is governed rather than left as an unmanaged HTTP callback.
The AI Gateway side of the platform adds a second, outbound example of SSE in practice, alongside the Event Gateway's inbound one. Not every LLM API call streams. A request can just as easily return one JSON response. But when an application requests a streaming completion from a provider like OpenAI or Anthropic, that response commonly comes back as a stream of tokens over SSE. When that call goes through the AI Gateway's App LLM Proxy, the proxy applies its token-based rate limiting and routing policies to that outbound traffic. It does not sit between the client and your API Gateway in a chain. It governs a separate, outbound hop that a chat feature makes on its own.
The result is one control plane covering ingress, broker-backed event traffic, and AI traffic alike, rather than one tool for REST, a second for streaming, and a third for whatever comes next. That does not make every governance question disappear on its own, but it does mean a webhook receiver or a WebSocket endpoint stops being the one API surface nobody remembered to secure.
Conclusion
Most production systems end up running all three transports for different jobs: webhooks for the notifications another company's backend needs to act on, a WebSocket for the one feature that genuinely needs two-way live data, and SSE for everything that only needs to stream toward the client, chat responses included. The choice is rarely which pattern is best. It is which job each endpoint is actually doing.
Whichever pattern a given endpoint uses, it is still an API, and it deserves the same authentication, rate limiting, and observability you would never skip on a REST endpoint. If you are already governing REST traffic through WSO2's API Platform and adding your first WebSocket or SSE endpoint, start with the API Platform documentation to see how the same control plane extends to it, rather than standing up a separate, ungoverned path for the new transport.
Frequently Asked Questions
What is the difference between event-driven APIs and REST APIs?
REST APIs answer a request the moment it is asked. Event-driven APIs send an update the moment an event happens, without the client asking each time.
What is the difference between an API and an event?
An API is the contract for interacting with a service. An event is a single occurrence, a fact that something happened, delivered as one message on top of that contract.
What are the key differences between event-driven and request-driven microservices?
Request-driven microservices call each other directly and wait for a response, coupling the caller to the callee's availability. Event-driven microservices publish and subscribe through a broker, so a service that is temporarily down does not block the ones publishing to it.
Is SSE the same as a WebSocket?
SSE is one-way, server to client, over a standard HTTP response. A WebSocket is bidirectional and requires an upgraded, persistent connection. Use SSE when only the server has something to send. Use a WebSocket when the client needs to send data back too.
Do I need a message broker to build an event-driven API?
Not always. A single webhook or SSE stream between two systems can work without one. A broker earns its place once you have multiple producers and consumers that need to publish and subscribe independently, rather than one system talking directly to another.