Data Event Listeners on Different Layers Explained

Aug 28, 2026

When I started getting deeper into distributed systems, I kept running into words like trigger, event listener, webhook, outbox, Kafka, eBPF, and XDP. At first, they all seemed to describe basically the same thing: something happens, something notices it, and something else reacts. The more I worked with them, though, the more I realized that while the basic idea is similar, they’re actually happening at completely different layers and solving completely different problems.

The easiest way I found to make sense of them was to stop asking what each thing is called and instead ask a much simpler question: where did the event happen, and what mechanism is responsible for reacting to it?

The first example is probably the easiest one to understand: a database trigger. A trigger lives inside the database itself and automatically runs when a particular database operation occurs. If I insert a payment into a table, for example, a trigger could automatically create an audit record as part of the database operation. The application doesn’t need to explicitly call the trigger. The database is watching its own state and reacting to changes internally.

That makes a DB trigger fundamentally different from an application event listener. If my Payment Service finishes processing a payment and publishes something like PaymentCompleted, another part of the application can listen for that event and perform some action, such as sending a notification. Nothing outside the application necessarily knows that this event exists. It’s just application-level code reacting to something that happened inside the application.

At this point, the distinction seemed pretty straightforward. Then I ran into the Outbox Pattern, and that’s where things became more interesting.

Imagine a payment service updating a payment in PostgreSQL and then publishing a PaymentCompleted event to Kafka. Those are two completely separate operations. The database update could succeed while the Kafka publish fails. Now the database says the payment completed, but the rest of the system never heard about it.

That’s the problem the outbox pattern is trying to solve. Instead of treating the database update and the event publication as two unrelated operations, the service performs both database writes inside the same transaction. It updates the payment and inserts an OutboxEvent saying that the payment completed. A separate publisher can then read those outbox records and publish them to Kafka.

The important thing I initially missed was that the outbox isn’t really a listener. It’s more like a durable record of an event that still needs to be propagated. The database becomes the place where the fact that something happened is safely recorded, and the outbox publisher takes care of getting that fact into the messaging system.

Once the event reaches Kafka, we’re at another layer entirely. Kafka isn’t sitting there watching my PostgreSQL database for changes. It’s storing and transporting messages that producers explicitly publish to it. Consumers then subscribe to topics and react when those messages arrive.

This also gives Kafka a property that a simple application listener doesn’t have. The message can sit in Kafka until the consumer is ready to process it. If my notification service is temporarily slower than the payment service, the producer doesn’t necessarily have to wait for the consumer. Kafka can buffer the events in between.

So at this point, I started thinking about the flow less as “a bunch of different listeners” and more as a chain of components, where each layer has a different responsibility. The application produces an event, the outbox makes its publication durable, Kafka transports and stores it, and the consumer reacts to it.

Then there are webhooks, which take the same basic idea and move it across a completely different boundary.

A webhook is essentially an event notification delivered through HTTP. If a payment completes, PayG might send an HTTP POST request to a merchant’s webhook endpoint. The merchant’s server doesn’t need to be part of the same application or consume the same Kafka topic. It simply exposes an HTTP endpoint that receives the notification.

That’s what makes a webhook different from a Kafka consumer. Both can react to the same event, but the delivery mechanism is completely different. Kafka is an internal messaging system where consumers read events from topics. A webhook is one system actively making an HTTP request to another system.

Then I went much further down the stack and ran into eBPF and XDP, which initially felt completely unrelated to everything else.

Linux has various hooks inside the kernel where programs can observe or react to events. eBPF provides a way to run programmable logic at many of these hooks. In networking, one particularly interesting hook is XDP, or Express Data Path, which operates very early in the packet-processing path.

So instead of something like a payment event travelling through an application and eventually reaching Kafka, the event here might simply be a network packet arriving at a network interface.

The flow is more like:

NIC
 ↓
Packet arrives
 ↓
XDP hook
 ↓
eBPF program
 ↓
Linux network stack

The eBPF program can inspect the packet and make a decision before the packet continues further through the network stack. For example, it could determine that a packet should be allowed to continue or dropped immediately.

This was probably the point where the terminology finally started making sense to me. eBPF isn’t really an “event listener” in the same sense as an application listener, and XDP isn’t an event system like Kafka. They’re mechanisms for attaching programmable logic to specific points in the kernel’s execution path.

Once I looked at everything from that perspective, the differences became much easier to see. A database trigger reacts to changes inside a database. An application listener reacts to application events. An outbox records events durably so they can be published later. A Kafka consumer reacts to messages in a messaging system. A webhook delivers an event to another system through HTTP. eBPF lets you run programmable logic in response to kernel events, while XDP provides an especially early hook in the networking path where that logic can run.

The interesting part is that they all still share the same basic pattern.

Something happens
       ↓
Something detects or receives it
       ↓
Something reacts

The mistake is assuming that because the pattern looks similar, the mechanisms are interchangeable. They’re not. The layer where the event originates, how it’s delivered, whether it’s durable, whether it can be replayed, and how far away the consumer is can completely change the design.

That also gave me a much simpler way to reason about new systems I come across. Whenever I see some new “event” mechanism, instead of immediately trying to memorize what it does, I ask three questions: what produced the event, where is that event happening, and how does the reaction get delivered?

Most of the time, those three questions are enough to tell me what I’m actually looking at.