Skip to main content

Setting Up Filters

This guide shows you how to use Filters to create intelligent webhook routing in Hooklistener. You'll learn how to write filter conditions, test them, and implement common filtering patterns.

What You'll Learn

  • How to write basic filter conditions
  • How to use different filter operators
  • How to test filters before deploying
  • Common filtering patterns and examples
  • Troubleshooting filter issues

Prerequisites

  • A Hooklistener account with an active Bridge
  • Understanding of Filters concept
  • Sample webhook payloads for testing

Basic Filter Setup

Adding Filters via Dashboard

Step 1: Edit Your Bridge

  1. Navigate to Bridges
  2. Select your Bridge
  3. Click "Edit"

Step 2: Add Bridge-Level Filter

To filter all webhooks before any destinations:

  1. In Bridge settings, find "Filters" section
  2. Click "Add Filter"
  3. Enter filter condition
  4. Click "Save"

Step 3: Add Destination-Level Filter

To filter for specific destinations:

  1. Select a Destination in your Bridge
  2. Find "Filters" section
  3. Click "Add Filter"
  4. Enter filter condition
  5. Click "Save"

Filter Syntax

Filters use JSON format:

{
"field.path": {
"$operator": "value"
}
}

Example - Filter by event type:

{
"body.type": {
"$eq": "payment.succeeded"
}
}

Common Filter Examples

Filter by Event Type

Only process "push" events from GitHub:

{
"body.type": { "$eq": "push" }
}

Or using header:

{
"headers.X-GitHub-Event": { "$eq": "push" }
}

Filter by Value Threshold

Only process payments above $100:

{
"body.amount": { "$gte": 10000 }
}

Note: Stripe amounts are in cents, so 10000 = $100.00

Filter by Multiple Conditions

Process only successful high-value payments:

[
{
"body.type": { "$eq": "payment_intent.succeeded" }
},
{
"body.data.object.amount": { "$gte": 100000 }
}
]

All conditions must match (implicit AND).

Filter by Array Membership

Process specific event types:

{
"body.action": {
"$in": ["opened", "reopened", "synchronize"]
}
}

Filter by String Content

Process webhooks containing "error":

{
"body.message": { "$contains": "error" }
}

Filter by Field Existence

Only process if optional field exists:

{
"body.metadata.customer_id": { "$exist": true }
}

Testing Filters

Using the Filter Test Endpoint

Before deploying, test your filters:

curl -X POST https://api.hooklistener.com/api/v1/filters/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": [
{
"body.amount": { "$gte": 100 }
}
],
"data": {
"body": {
"amount": 150,
"currency": "USD"
}
}
}'

Response:

{
"match": true,
"details": {
"body.amount": {
"operator": "$gte",
"expected": 100,
"actual": 150,
"result": true
}
}
}

Testing in Dashboard

  1. Go to Bridge → Edit → Filters
  2. Click "Test Filters"
  3. Paste a sample webhook payload
  4. See which conditions match or fail

Filter Operators Reference

Equality Operators

$eq - Equals

{"body.status": {"$eq": "active"}}

$neq - Not equals

{"body.status": {"$neq": "deleted"}}

Comparison Operators

$gt - Greater than

{"body.price": {"$gt": 100}}

$gte - Greater than or equal

{"body.price": {"$gte": 100}}

$lt - Less than

{"body.quantity": {"$lt": 10}}

$lte - Less than or equal

{"body.quantity": {"$lte": 10}}

String Operators

$contains - String contains

{"body.message": {"$contains": "urgent"}}

$startsWith - String starts with

{"body.event_type": {"$startsWith": "payment"}}

$endsWith - String ends with

{"body.email": {"$endsWith": "@company.com"}}

$regex - Regular expression

{"body.phone": {"$regex": "^\\+1[0-9]{10}$"}}

Array Operators

$in - Value in array

{"body.status": {"$in": ["active", "pending"]}}

$nin - Value not in array

{"body.status": {"$nin": ["deleted", "archived"]}}

Special Operators

$exist - Field exists

{"body.optional_field": {"$exist": true}}

$ref - Compare two fields

{"body.updated_at": {"$ref": "body.created_at"}}

Logical Operators

$or - Any condition matches

{
"$or": [
{"body.priority": {"$eq": "high"}},
{"body.amount": {"$gte": 10000}}
]
}

$and - All conditions match

{
"$and": [
{"body.type": {"$eq": "payment"}},
{"body.status": {"$eq": "succeeded"}}
]
}

$not - Condition does not match

{
"$not": {
"body.test": {"$eq": true}
}
}

Real-World Filter Patterns

GitHub Pull Request Notifications

Only notify about opened PRs in main branch:

[
{
"headers.X-GitHub-Event": { "$eq": "pull_request" }
},
{
"body.action": { "$in": ["opened", "reopened"] }
},
{
"body.pull_request.base.ref": { "$eq": "main" }
}
]

Stripe High-Value Payments

Alert for payments over $1,000:

[
{
"body.type": { "$eq": "payment_intent.succeeded" }
},
{
"body.data.object.amount": { "$gte": 100000 }
},
{
"body.data.object.currency": { "$eq": "usd" }
}
]

Shopify VIP Customers

Process orders from VIP customers:

[
{
"body.type": { "$eq": "order_create" }
},
{
"$or": [
{"body.customer.tags": {"$contains": "VIP"}},
{"body.customer.total_spent": {"$gte": 10000}}
]
}
]

Production-Only Events

Filter out test and development webhooks:

[
{
"body.environment": { "$eq": "production" }
},
{
"$not": {
"body.test": { "$eq": true }
}
},
{
"$not": {
"body.email": { "$endsWith": "@test.com" }
}
}
]

Error Detection

Route failed events to error handler:

{
"$or": [
{"body.status": {"$in": ["failed", "error", "rejected"]}},
{"body.error_code": {"$exist": true}},
{"body.success": {"$eq": false}}
]
}

Geographic Routing

Route by customer country:

{
"body.customer.country": { "$in": ["US", "CA", "MX"] }
}

Priority-Based Routing

Route critical events to priority queue:

{
"$or": [
{"body.priority": {"$gte": 5}},
{"body.severity": {"$eq": "critical"}},
{"body.tags": {"$contains": "urgent"}}
]
}

Advanced Filter Techniques

Nested Field Access

Access deeply nested fields:

{
"body.data.customer.subscription.plan.tier": { "$eq": "premium" }
}

Array Element Access

Access specific array elements:

{
"body.items[0].price": { "$gt": 100 }
}

Combining Filters

Use multiple filter blocks for complex logic:

[
{
"$or": [
{"body.type": {"$eq": "payment"}},
{"body.type": {"$eq": "refund"}}
]
},
{
"body.amount": { "$gte": 100 }
},
{
"$not": {
"body.test": { "$eq": true }
}
}
]

Case-Insensitive Matching

Use regex for case-insensitive matching:

{
"body.email": { "$regex": "(?i)@company\\.com$" }
}

Date-Based Filtering

If webhook includes ISO dates:

{
"body.created_at": { "$gte": "2024-01-01T00:00:00Z" }
}

Filter Placement Strategies

Bridge-Level Filters

Use for:

  • Filtering out test webhooks
  • Environment-specific filtering
  • Excluding unwanted webhook types

Example:

[
{"body.environment": {"$eq": "production"}},
{"$not": {"body.test": {"$eq": true}}}
]

Effect: Webhooks that don't match are rejected entirely.

Destination-Level Filters

Use for:

  • Routing specific webhooks to specific destinations
  • Priority-based routing
  • Event-type-specific handling

Example:

{
"destinations": [
{
"name": "High Priority",
"filters": [
{"body.priority": {"$eq": "high"}}
]
},
{
"name": "Normal Priority",
"filters": [
{"body.priority": {"$eq": "normal"}}
]
},
{
"name": "Catch All"
// No filters - receives everything
}
]
}

Effect: Webhooks are routed to matching destinations only.

Troubleshooting Filters

Filters Not Matching

Problem: Webhooks aren't matching expected filters

Debugging Steps:

  1. Check the actual payload:
# Get a recent webhook event
curl -X GET https://api.hooklistener.com/api/v1/requests?limit=1 \
-H "Authorization: Bearer YOUR_API_KEY"
  1. Verify field paths:
  • Use the exact structure from the payload
  • Check for typos: body.data.id vs body.id
  • Case sensitivity matters: body.Status vs body.status
  1. Check data types:
  • String vs number: "100" vs 100
  • Boolean vs string: true vs "true"
  • Array vs single value
  1. Test the filter:
curl -X POST https://api.hooklistener.com/api/v1/filters/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": YOUR_FILTERS,
"data": ACTUAL_WEBHOOK_PAYLOAD
}'

Filters Matching Too Much

Problem: More webhooks matching than expected

Solutions:

  1. Add more specific conditions:
// Before: Too broad
{"body.type": {"$contains": "payment"}}

// After: More specific
{"body.type": {"$eq": "payment.succeeded"}}
  1. Add exclusions:
[
{"body.type": {"$eq": "payment.succeeded"}},
{"$not": {"body.test": {"$eq": true}}}
]
  1. Use stricter operators:
// Instead of $contains
{"body.type": {"$startsWith": "payment."}}

// Instead of $gte
{"body.amount": {"$gt": 100}}

Unexpected Filter Behavior

Common Issues:

1. Missing Fields:

// Check if field exists first
[
{"body.optional_field": {"$exist": true}},
{"body.optional_field": {"$eq": "value"}}
]

2. null vs undefined:

// Explicitly check for null
{"body.field": {"$neq": null}}

3. Array vs Object:

// For arrays, use $in
{"body.tags": {"$in": ["important"]}}

// For objects, use dot notation
{"body.metadata.key": {"$eq": "value"}}

Best Practices

Filter Design

  1. Keep filters simple

    • Easier to understand and maintain
    • Better performance
    • Fewer bugs
  2. Test thoroughly

    • Use filter test endpoint
    • Test with real webhook data
    • Verify edge cases
  3. Document complex filters

    • Add comments in documentation
    • Explain the logic
    • Note expected behavior

Performance

  1. Filter early

    • Use bridge-level filters to reject unwanted webhooks
    • Reduces unnecessary processing
    • Saves resources
  2. Use efficient operators

    • $eq is faster than $regex
    • $in with small arrays is efficient
    • Avoid complex regex patterns
  3. Order conditions strategically

    • Most selective conditions first
    • Quick-to-evaluate conditions early
    • Expensive operations last

Maintainability

  1. Use clear field paths

    • Match your webhook structure
    • Use consistent naming
    • Document non-obvious paths
  2. Avoid brittle filters

    • Don't rely on exact string matches that might change
    • Use $contains or $startsWith when appropriate
    • Handle data variations
  3. Version your filters

    • Test changes in staging first
    • Document filter changes
    • Keep backups of working configs

Filter Examples by Use Case

E-commerce

High-value order notifications:

[
{"body.event": {"$eq": "order.created"}},
{"body.total_price": {"$gte": 500}}
]

Abandoned cart recovery:

[
{"body.event": {"$eq": "cart.abandoned"}},
{"body.cart_value": {"$gte": 50}},
{"body.customer.email": {"$exist": true}}
]

SaaS Applications

New user signups:

[
{"body.event": {"$eq": "user.created"}},
{"$not": {"body.user.test": {"$eq": true}}}
]

Subscription upgrades:

[
{"body.event": {"$eq": "subscription.updated"}},
{"body.previous_plan.tier": {"$eq": "free"}},
{"body.new_plan.tier": {"$in": ["pro", "enterprise"]}}
]

CI/CD Pipelines

Failed builds:

[
{"body.event": {"$eq": "build.completed"}},
{"body.status": {"$in": ["failed", "error"]}},
{"body.branch": {"$eq": "main"}}
]

Deployment success:

[
{"body.event": {"$eq": "deployment.completed"}},
{"body.status": {"$eq": "success"}},
{"body.environment": {"$eq": "production"}}
]

Next Steps

Now that you understand filters, continue with:

  1. Use Transformations to modify payloads
  2. Monitor Events to see filter results
  3. Manage Issues when filters misbehave
  4. Build Complex Bridges with advanced filtering

Filters are a powerful tool for intelligent webhook routing. Practice with simple filters first, then build up to more complex patterns as you become comfortable with the syntax and operators.