# Server-Side Event Filtering

Server-side filtering lets you subscribe to specific event types with rulekit expressions, reducing bandwidth and eliminating client-side filtering. Instead of receiving all events and discarding most, tell Qtap exactly what you want.

{% hint style="info" %}
**New in v0.14.0**: Server-side filtering requires Qtap v0.14.0 or later. Earlier versions stream all events without filtering.
{% endhint %}

***

## Why Server-Side Filtering?

| Without Filtering                      | With Filtering                   |
| -------------------------------------- | -------------------------------- |
| Receive all events, filter client-side | Receive only matching events     |
| High bandwidth in busy environments    | Minimal bandwidth usage          |
| Client processes every event           | Server handles filtering         |
| Same data for all consumers            | Different filters per connection |

Use cases:

* **High-traffic environments**: Subscribe only to errors instead of all HTTP transactions
* **Targeted monitoring**: Watch specific containers or processes
* **Multi-consumer scenarios**: Different dashboards subscribe to different event types

***

## Quick Start

Subscribe to HTTP errors only (status >= 400):

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "http": "res.status >= 400"
    }
  }'
```

The response stream starts with `system.connected` confirming your subscription:

```
event: system.connected
data: {"data":{"topics":{"http":"res.status >= 400"}},"ts":"2026-01-20T19:36:50.978Z"}
```

***

## API Reference

### Endpoint

```
POST http://localhost:10001/devtools/api/events
Content-Type: application/json
```

### Subscription Payload

The JSON payload uses a `topics` object to specify subscriptions:

```json
{
  "topics": {
    "process": "*",
    "connection": "*",
    "http": "res.status >= 400"
  }
}
```

{% hint style="warning" %}
**Topic names must be lowercase**: Use `http`, `process`, `connection` - not capitalized versions.
{% endhint %}

### Subscription Semantics

| Payload            | Behavior                             |
| ------------------ | ------------------------------------ |
| `"*"`              | Subscribe to all events of that type |
| Filter expression  | Subscribe to matching events only    |
| Omit topic         | No events of that type               |
| Empty payload `{}` | Default: all topics with `"*"`       |

### Examples

```bash
# All events (default behavior)
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{}'

# HTTP only, no process or connection events
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "http": "*"
    }
  }'

# Processes only
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "process": "*"
    }
  }'
```

***

## Filter Fields by Topic

Native properties (belonging to the event type) have no prefix. Relational properties (from associated entities) are prefixed.

{% hint style="info" %}
**Verified Fields**: Fields marked with ✓ have been tested and verified to work with server-side filtering. Fields in the "Not Available" section are present in event data but cannot be used for server-side filtering.
{% endhint %}

### Process Events

Fields available for `process.started` and `process.stopped` events:

| Field                    | Description                       | Example                       |
| ------------------------ | --------------------------------- | ----------------------------- |
| `binary` ✓               | Executable name                   | `python3.11`, `node`, `nginx` |
| `path` ✓                 | Full path to binary               | `/usr/local/bin/python3.11`   |
| `hostname` ✓             | Process hostname                  | `payment-service`             |
| `user.name` ✓            | Username                          | `root`, `www-data`            |
| `user.id` ✓              | User ID                           | `0`, `1000`                   |
| `container.name` ✓       | Container name                    | `my-app`                      |
| `container.id` ✓         | Container ID                      | `1781322a598f`                |
| `container.image` ✓      | Container image                   | `python:3.11-slim`            |
| `container.labels.<key>` | Container label value             | `container.labels.app`        |
| `pod.name`               | Pod name (Kubernetes only)        | `payment-service-7d8f9`       |
| `pod.namespace`          | Pod namespace (Kubernetes only)   | `production`                  |
| `pod.labels.<key>`       | Pod label value (Kubernetes only) | `pod.labels.app`              |

### Connection Events

Native connection properties + relational `process.*` fields:

| Field                    | Description                       | Example                                         |
| ------------------------ | --------------------------------- | ----------------------------------------------- |
| `direction` ✓            | Connection direction              | `egress-external`, `egress-internal`, `ingress` |
| `protocol` ✓             | L7 protocol                       | `http1`, `http2`, `other`                       |
| `type` ✓                 | Socket type                       | `tcp`, `udp`                                    |
| `src.ip` ✓               | Source IP address                 | `172.18.0.7`                                    |
| `src.port` ✓             | Source port                       | `34438`                                         |
| `dst.ip` ✓               | Destination IP address            | `52.4.128.73`                                   |
| `dst.port` ✓             | Destination port                  | `443`, `80`                                     |
| `process.binary` ✓       | Process executable name           | `python3.11`                                    |
| `process.path` ✓         | Full executable path              | `/usr/local/bin/python3.11`                     |
| `process.hostname` ✓     | Process hostname                  | `payment-service`                               |
| `process.user.name` ✓    | Username                          | `root`                                          |
| `process.user.id` ✓      | User ID                           | `0`                                             |
| `container.name` ✓       | Container name                    | `payment-service`                               |
| `container.id` ✓         | Container ID                      | `1781322a598f`                                  |
| `container.image` ✓      | Container image                   | `python:3.11-slim`                              |
| `container.labels.<key>` | Container label value             | `container.labels.app`                          |
| `pod.name`               | Pod name (Kubernetes only)        | `payment-service-7d8f9`                         |
| `pod.namespace`          | Pod namespace (Kubernetes only)   | `production`                                    |
| `pod.labels.<key>`       | Pod label value (Kubernetes only) | `pod.labels.app`                                |

**Not available for server-side filtering** (present in event data only):

* `dst.domain` - Use `req.host` on HTTP topic instead
* `tls.enabled`, `tls.version`, `tls.sni` - TLS info available in event data

### HTTP Events

Native HTTP properties (`req.*`/`res.*`) + relational connection/process fields:

| Field                    | Description                       | Example                                         |
| ------------------------ | --------------------------------- | ----------------------------------------------- |
| `req.method` ✓           | HTTP method                       | `GET`, `POST`, `PUT`, `DELETE`                  |
| `req.host` ✓             | Request host                      | `api.stripe.com`                                |
| `res.status` ✓           | Response status code              | `200`, `404`, `500`                             |
| `direction` ✓            | Connection direction              | `egress-external`, `egress-internal`, `ingress` |
| `protocol` ✓             | L7 protocol                       | `http1`, `http2`                                |
| `src.ip` ✓               | Source IP address                 | `172.18.0.7`                                    |
| `src.port` ✓             | Source port                       | `34438`                                         |
| `dst.ip` ✓               | Destination IP address            | `52.4.128.73`                                   |
| `dst.port` ✓             | Destination port                  | `443`, `80`                                     |
| `process.binary` ✓       | Process executable name           | `python3.11`                                    |
| `process.path` ✓         | Full executable path              | `/usr/local/bin/python3.11`                     |
| `process.hostname` ✓     | Process hostname                  | `payment-service`                               |
| `process.user.name` ✓    | Username                          | `root`                                          |
| `container.name` ✓       | Container name                    | `payment-service`                               |
| `container.id` ✓         | Container ID                      | `1781322a598f`                                  |
| `container.image` ✓      | Container image                   | `python:3.11-slim`                              |
| `container.labels.<key>` | Container label value             | `container.labels.app`                          |
| `pod.name`               | Pod name (Kubernetes only)        | `payment-service-7d8f9`                         |
| `pod.namespace`          | Pod namespace (Kubernetes only)   | `production`                                    |
| `pod.labels.<key>`       | Pod label value (Kubernetes only) | `pod.labels.app`                                |

**Not available for server-side filtering** (present in event data only):

* `req.path`, `req.url` - Full path/URL available in decoded event payload
* `dst.domain` - Use `req.host` instead
* `tls.enabled`, `tls.version`, `tls.sni` - TLS info available in event data
* `process.user.id` - Use `process.user.name` instead

{% hint style="info" %}
**Cross-Entity Filtering**: Filters can apply across related entities. For example, filtering HTTP events by `container.name == "nginx"` will match HTTP requests from that container via connection cache lookup.
{% endhint %}

***

## Operators

### Comparison Operators

| Operator | Description      | Example               |
| -------- | ---------------- | --------------------- |
| `==`     | Equal            | `res.status == 200`   |
| `!=`     | Not equal        | `req.method != "GET"` |
| `>`      | Greater than     | `res.status > 399`    |
| `>=`     | Greater or equal | `res.status >= 400`   |
| `<`      | Less than        | `res.status < 400`    |
| `<=`     | Less or equal    | `src.port <= 1024`    |

### String Operators

| Operator   | Description     | Example                               |
| ---------- | --------------- | ------------------------------------- |
| `contains` | Substring match | `req.host contains "api."`            |
| `matches`  | Regex match     | `req.host matches /api\.[a-z]+\.com/` |

### Logical Operators

| Operator | Description      | Example                                      |
| -------- | ---------------- | -------------------------------------------- |
| `AND`    | Both conditions  | `res.status >= 400 AND req.method == "POST"` |
| `OR`     | Either condition | `res.status == 500 OR res.status == 503`     |
| `NOT`    | Negate condition | `NOT req.method == "GET"`                    |

### Special Operators

| Operator | Description   | Example                                   |
| -------- | ------------- | ----------------------------------------- |
| `in`     | Value in list | `req.method in ["POST", "PUT", "DELETE"]` |
| `in`     | IP in CIDR    | `dst.ip in "10.0.0.0/8"`                  |

***

## Custom Functions

### in\_zone(domain, zone)

Check if a domain belongs to a DNS zone:

```
in_zone(req.host, "stripe.com")
```

Matches `api.stripe.com`, `dashboard.stripe.com`, `stripe.com`.

### zone(domain)

Extract the zone from a domain for comparison:

```
zone(req.host) == "stripe.com"
```

***

## Examples

### Subscribe to All Events (Default)

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{}'
```

Response confirms subscription to all topics:

```
event: system.connected
data: {"data":{"topics":{"connection":"*","http":"*","process":"*"}}}
```

### HTTP Errors Only

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "http": "res.status >= 400"
    }
  }'
```

### Specific Container Traffic

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "http": "container.name == \"stripe-payment-service\"",
      "connection": "container.name == \"stripe-payment-service\""
    }
  }'
```

### HTTPS Connections Only (Port 443)

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "connection": "dst.port == 443"
    }
  }'
```

### Specific Process Binary

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "process": "binary == \"python3.11\""
    }
  }'
```

### Multiple Topics with Different Filters

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "process": "*",
      "connection": "dst.port == 443",
      "http": "res.status >= 400"
    }
  }'
```

### Cross-Entity Filtering (HTTP by Process)

Filter HTTP transactions by the originating process:

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "http": "process.binary == \"python3.11\""
    }
  }'
```

### Complex Filters

Combine multiple conditions:

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{
    "topics": {
      "http": "res.status >= 500 AND req.method in [\"POST\", \"PUT\", \"DELETE\"]"
    }
  }'
```

***

## Best Practices

### Start Broad, Narrow Down

Begin with permissive filters to verify events are flowing, then add constraints:

```bash
# Step 1: Verify HTTP events are captured
curl -sN -X POST ... -d '{"topics": {"http": "*"}}'

# Step 2: Add status filter
curl -sN -X POST ... -d '{"topics": {"http": "res.status >= 400"}}'

# Step 3: Add container filter
curl -sN -X POST ... -d '{"topics": {"http": "res.status >= 400 AND container.name == \"my-app\""}}'
```

### Verify Subscriptions

Always check the `system.connected` event to confirm your filters were accepted:

```bash
curl -sN -X POST http://localhost:10001/devtools/api/events \
  -H "Content-Type: application/json" \
  -d '{"topics": {"http": "res.status >= 400"}}' | head -3
```

Expected output:

```
event: system.connected
data: {"data":{"topics":{"http":"res.status >= 400"}}}
```

### Use Specific Topics

Only subscribe to event types you need:

```bash
# Good: Only HTTP events
curl -sN -X POST ... -d '{"topics": {"http": "*"}}'

# Wasteful: All events when you only need HTTP
curl -sN -X POST ... -d '{}'
```

***

## Troubleshooting

### No Events Received

1. **Check subscription**: Verify `system.connected` shows your topics
2. **Generate traffic**: The filter might be too restrictive
3. **Test with `"*"`**: Remove filters to confirm events are flowing
4. **Check Qtap version**: Server-side filtering requires v0.14.0+

### Filter Syntax Errors

Invalid filters cause the connection to fail. Test expressions incrementally:

```bash
# Start simple
curl -sN -X POST ... -d '{"topics": {"http": "res.status >= 400"}}'

# Then add complexity
curl -sN -X POST ... -d '{"topics": {"http": "res.status >= 400 AND process.binary == \"python3.11\""}}'
```

### Common Mistakes

| Mistake                          | Fix                                                  |
| -------------------------------- | ---------------------------------------------------- |
| `"HTTP"` instead of `"http"`     | Use lowercase: `"http"`, `"process"`, `"connection"` |
| Flat payload `{"http": "..."}`   | Use topics wrapper: `{"topics": {"http": "..."}}`    |
| `status` instead of `res.status` | Use full field path: `res.status >= 400`             |
| `method` instead of `req.method` | Use full field path: `req.method == "POST"`          |
| Wrong port (10002)               | Use port `10001`                                     |

### Events Don't Match Filter

1. Verify field names match the schema (case-sensitive)
2. Check string values are quoted correctly
3. Use `"*"` first to see actual field values in events
4. Test filter logic with simpler conditions

***

## Related Documentation

* [DevTools API](/guides/devtools-guides/devtools-api.md) - Full API reference and event schemas
* [Getting Started with DevTools](/guides/devtools-guides/getting-started-with-devtools.md) - Initial setup guide


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.qpoint.io/guides/devtools-guides/server-side-event-filtering.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
