Filters
Filters are conditional routing rules that determine which webhooks should be processed by your Bridges and Destinations. Using filters, you can create sophisticated routing logic to send specific webhooks to specific destinations based on their content.
Overview
Filters evaluate webhook data against conditions you define. When all filter conditions match, the webhook is processed. When any condition fails, the webhook is skipped for that Bridge or Destination.
Key features:
- Powerful query language: MongoDB-style operators for flexible matching
- Nested field access: Query deep into JSON payloads using dot notation
- Multiple data types: Numbers, strings, booleans, arrays, objects
- Logical operations: AND, OR, NOT for complex conditions
- Real-time testing: Validate filters before deploying
- Performance optimized: Minimal impact on webhook delivery latency
When to Use Filters
Common use cases:
- Event type routing: Send different event types to different destinations
- Threshold-based alerts: Notify only when values exceed limits
- Environment filtering: Process only production or staging webhooks
- Conditional transformations: Apply transformations based on content
- Error handling: Route failed events to special destinations
- Priority routing: Direct high-priority events to faster queues
- Geographic filtering: Route based on location data
- Customer segmentation: Handle VIP vs standard customers differently
Filter Syntax
Filters use a JSON-based query language similar to MongoDB:
{
"field.path": {
"$operator": "value"
}
}
Basic Example
Only process webhooks where the event type is "payment.succeeded":
{
"filters": [
{
"body.type": {
"$eq": "payment.succeeded"
}
}
]
}
Multiple Conditions
All conditions must match (implicit AND):
{
"filters": [
{
"body.type": { "$eq": "payment.succeeded" }
},
{
"body.amount": { "$gte": 100 }
},
{
"body.currency": { "$eq": "USD" }
}
]
}
Field Paths
Access webhook data using dot notation:
Top-level fields:
{
"method": { "$eq": "POST" }
}
Nested fields:
{
"body.user.email": { "$eq": "[email protected]" }
}
Array elements:
{
"body.items[0].price": { "$gt": 100 }
}
Deep nesting:
{
"body.data.customer.subscription.plan.tier": { "$eq": "premium" }
}
Available Fields
Filters can access all parts of the webhook request:
method: HTTP method (GET, POST, PUT, etc.)url: Request URLheaders.Header-Name: Request headersbody: Parsed JSON body or raw stringbody.field.nested: Any field within the JSON body
Comparison Operators
Equality
$eq: Equals
{
"body.status": { "$eq": "active" }
}
$neq: Not equals
{
"body.status": { "$neq": "deleted" }
}
Numeric Comparisons
$gt: Greater than
{
"body.amount": { "$gt": 100 }
}
$gte: Greater than or equal
{
"body.amount": { "$gte": 100 }
}
$lt: Less than
{
"body.quantity": { "$lt": 10 }
}
$lte: Less than or equal
{
"body.quantity": { "$lte": 10 }
}
String Operators
$contains: String contains substring
{
"body.message": { "$contains": "error" }
}
$startsWith: String starts with prefix
{
"body.event_type": { "$startsWith": "payment" }
}
$endsWith: String ends with suffix
{
"body.email": { "$endsWith": "@company.com" }
}
$regex: Regular expression matching
{
"body.phone": { "$regex": "^\\+1[0-9]{10}$" }
}
Array Operators
$in: Value is in array
{
"body.status": { "$in": ["active", "pending", "processing"] }
}
$nin: Value is not in array
{
"body.status": { "$nin": ["deleted", "archived"] }
}
Existence Operator
$exist: Field exists or doesn't exist
{
"body.optional_field": { "$exist": true }
}
{
"body.deleted_at": { "$exist": false }
}
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" } },
{ "body.amount": { "$gte": 100 } }
]
}
$not: Condition does not match
{
"$not": {
"body.test": { "$eq": true }
}
}
Reference Operator
$ref: Compare two fields within the same payload
{
"body.updated_at": { "$ref": "body.created_at" }
}
This matches when updated_at equals created_at (new records).
Complex Filter Examples
Event Type Routing
Route different GitHub event types to different destinations:
{
"destinations": [
{
"name": "PR Notifications",
"type": "slack",
"webhook_url": "{{SLACK_PR_WEBHOOK}}",
"filters": [
{ "body.action": { "$in": ["opened", "reopened"] } },
{ "headers.X-GitHub-Event": { "$eq": "pull_request" } }
]
},
{
"name": "Issue Notifications",
"type": "slack",
"webhook_url": "{{SLACK_ISSUE_WEBHOOK}}",
"filters": [
{ "headers.X-GitHub-Event": { "$eq": "issues" } }
]
}
]
}
Threshold-Based Alerts
Alert for high-value transactions:
{
"filters": [
{
"body.type": { "$eq": "payment.succeeded" }
},
{
"$or": [
{ "body.amount": { "$gte": 100000 } },
{ "body.customer.vip": { "$eq": true } }
]
}
]
}
Environment-Specific Routing
Process only production events:
{
"filters": [
{
"$and": [
{ "body.environment": { "$eq": "production" } },
{ "body.test": { "$exist": false } }
]
}
]
}
Error Detection
Route failed events to error handling:
{
"filters": [
{
"$or": [
{ "body.status": { "$in": ["failed", "error", "rejected"] } },
{ "body.error_code": { "$exist": true } },
{ "body.success": { "$eq": false } }
]
}
]
}
Geographic Filtering
Route based on country:
{
"destinations": [
{
"name": "US Region",
"type": "http",
"url": "https://us-api.company.com/webhooks",
"filters": [
{ "body.country": { "$eq": "US" } }
]
},
{
"name": "EU Region",
"type": "http",
"url": "https://eu-api.company.com/webhooks",
"filters": [
{ "body.country": { "$in": ["GB", "FR", "DE", "IT", "ES"] } }
]
}
]
}
Customer Segmentation
Handle VIP customers differently:
{
"destinations": [
{
"name": "VIP Customer Queue",
"type": "http",
"url": "https://api.company.com/vip-queue",
"filters": [
{
"$or": [
{ "body.customer.tier": { "$eq": "premium" } },
{ "body.customer.lifetime_value": { "$gte": 50000 } }
]
}
]
}
]
}
Content-Based Filtering
Filter by message content:
{
"filters": [
{
"$and": [
{ "body.message": { "$contains": "urgent" } },
{ "body.priority": { "$gte": 5 } },
{ "$not": { "body.message": { "$contains": "test" } } }
]
}
]
}
Filter Placement
Bridge-Level Filters
Applied before any destinations are processed:
{
"name": "Production Events Only",
"filters": [
{ "body.environment": { "$eq": "production" } }
],
"destinations": [...]
}
Use case: Filter out unwanted webhooks entirely (test events, staging, etc.)
Behavior:
- If filters don't match, webhook is rejected and not processed at all
- Event is logged as "filtered out"
- No destinations are attempted
Destination-Level Filters
Applied per destination:
{
"destinations": [
{
"name": "High Priority",
"type": "http",
"url": "https://api.company.com/high-priority",
"filters": [
{ "body.priority": { "$eq": "high" } }
]
},
{
"name": "All Events",
"type": "http",
"url": "https://api.company.com/all-events"
}
]
}
Use case: Route different webhooks to different destinations
Behavior:
- If filters don't match, delivery to that specific destination is skipped
- Other destinations are still attempted
- Event is logged as "filtered for destination X"
Testing Filters
Via API
Test filter syntax before deploying:
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
}
}
}
Via Dashboard
- Go to Bridges → Select Bridge → Edit
- Add or modify filters
- Click "Test Filters"
- Paste sample webhook payload
- See which filters match or fail
Best Practices
Performance
-
Keep filters simple
- Simpler filters execute faster
- Avoid deeply nested logical operations
- Use specific field paths
-
Place selective filters early
- Most selective conditions first
- Filter out majority of unwanted webhooks quickly
- Reduce unnecessary processing
-
Use appropriate operators
$eqis faster than$regex$inwith small arrays is efficient- Avoid complex regular expressions
Maintainability
-
Use descriptive conditions
- Clear field names and values
- Comment complex filter logic in documentation
- Group related conditions logically
-
Test thoroughly
- Use filter test endpoint
- Test with real webhook payloads
- Verify edge cases
-
Document filter intent
- Explain why filters exist
- Note expected behavior
- Track changes to filter logic
Reliability
-
Handle missing fields
- Use
$existto check field presence - Consider default values
- Don't assume fields always exist
- Use
-
Account for data variations
- Different event types have different structures
- Use
$orfor acceptable variations - Test with multiple payload samples
-
Avoid brittle filters
- Don't rely on exact string matches if values might change
- Use
$containsor$startsWithwhen appropriate - Consider using
$infor known value sets
Common Patterns
Multi-Stage Filtering
Bridge-level filter + destination-level filters:
{
"name": "Payment Processing",
"filters": [
{ "body.type": { "$startsWith": "payment" } }
],
"destinations": [
{
"name": "Successful Payments",
"filters": [
{ "body.status": { "$eq": "succeeded" } }
]
},
{
"name": "Failed Payments",
"filters": [
{ "body.status": { "$in": ["failed", "declined"] } }
]
}
]
}
Fallback Routing
Primary destination with filtered routing, fallback catches all:
{
"destinations": [
{
"name": "Premium Customers",
"filters": [
{ "body.customer.tier": { "$eq": "premium" } }
]
},
{
"name": "Standard Customers",
"filters": [
{ "body.customer.tier": { "$eq": "standard" } }
]
},
{
"name": "All Other Events"
// No filters - catches everything
}
]
}
Exclusion Filtering
Filter out unwanted events:
{
"filters": [
{
"$not": {
"body.event_type": { "$in": ["ping", "test", "health_check"] }
}
},
{
"body.environment": { "$neq": "development" }
}
]
}
Troubleshooting
Filters Not Matching Expected Webhooks
Check field paths:
# Verify the exact structure of your webhook
curl -X POST https://api.hooklistener.com/api/v1/filters/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": [],
"data": <paste-your-webhook-here>
}'
Common issues:
- Incorrect field path:
body.data.user.idvsbody.user.id - Case sensitivity:
body.Statusvsbody.status - Data type mismatch:
"100"(string) vs100(number) - Missing fields: Field doesn't exist in all webhooks
Filters Matching Too Many Webhooks
Too broad conditions:
- Use more specific operators
- Add additional conditions (implicit AND)
- Check for edge cases
Review event history:
- Look at webhooks that incorrectly matched
- Identify common patterns
- Refine filter logic
Performance Issues
Complex filters:
- Simplify logical operations
- Reduce nesting depth
- Split into multiple simpler filters
Regular expressions:
- Use simpler operators when possible
- Optimize regex patterns
- Consider string operators instead
Filter Limitations
Not supported:
- External API calls or database queries
- Dynamic/computed filter values
- Cross-webhook comparisons
- Time-based filtering (use current payload data instead)
Workarounds:
- Use transformations to enrich data before filtering
- Add timestamp/date fields to payload
- Pre-process webhooks before sending to Hooklistener
Next Steps
Now that you understand Filters, explore related features:
- Use Transformations to modify payloads before filtering
- Build Bridges to combine filters with routing
- Monitor Events to see filter results
- Track Issues when filters behave unexpectedly
Filters are a powerful tool for creating intelligent webhook routing logic. By combining filters with Sources, Destinations, and Transformations, you can build sophisticated automation workflows that handle different webhook types, priorities, and destinations with precision.