> For the complete documentation index, see [llms.txt](https://docs.qpoint.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.qpoint.io/guides/devtools-guides/server-side-event-filtering.md).

# 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
