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
- Navigate to Bridges
- Select your Bridge
- Click "Edit"
Step 2: Add Bridge-Level Filter
To filter all webhooks before any destinations:
- In Bridge settings, find "Filters" section
- Click "Add Filter"
- Enter filter condition
- Click "Save"
Step 3: Add Destination-Level Filter
To filter for specific destinations:
- Select a Destination in your Bridge
- Find "Filters" section
- Click "Add Filter"
- Enter filter condition
- 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
- Go to Bridge → Edit → Filters
- Click "Test Filters"
- Paste a sample webhook payload
- 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:
- 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"
- Verify field paths:
- Use the exact structure from the payload
- Check for typos:
body.data.idvsbody.id - Case sensitivity matters:
body.Statusvsbody.status
- Check data types:
- String vs number:
"100"vs100 - Boolean vs string:
truevs"true" - Array vs single value
- 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:
- Add more specific conditions:
// Before: Too broad
{"body.type": {"$contains": "payment"}}
// After: More specific
{"body.type": {"$eq": "payment.succeeded"}}
- Add exclusions:
[
{"body.type": {"$eq": "payment.succeeded"}},
{"$not": {"body.test": {"$eq": true}}}
]
- 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
-
Keep filters simple
- Easier to understand and maintain
- Better performance
- Fewer bugs
-
Test thoroughly
- Use filter test endpoint
- Test with real webhook data
- Verify edge cases
-
Document complex filters
- Add comments in documentation
- Explain the logic
- Note expected behavior
Performance
-
Filter early
- Use bridge-level filters to reject unwanted webhooks
- Reduces unnecessary processing
- Saves resources
-
Use efficient operators
$eqis faster than$regex$inwith small arrays is efficient- Avoid complex regex patterns
-
Order conditions strategically
- Most selective conditions first
- Quick-to-evaluate conditions early
- Expensive operations last
Maintainability
-
Use clear field paths
- Match your webhook structure
- Use consistent naming
- Document non-obvious paths
-
Avoid brittle filters
- Don't rely on exact string matches that might change
- Use
$containsor$startsWithwhen appropriate - Handle data variations
-
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:
- Use Transformations to modify payloads
- Monitor Events to see filter results
- Manage Issues when filters misbehave
- 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.