Event Filtering
Smart Webhooks can filter incoming events so that only specific event types trigger AI processing. Use the source_events array to define which events your webhook cares about. Unmatched events are silently skipped.
How Filtering Works
When a webhook payload arrives, the execution engine extracts the event type from the payload by checking these fields in order:
payload.typepayload.event.typepayload.event_typepayload.action
The extracted event type is compared against the source_events array. If it matches any entry, the webhook is processed. If not, it is skipped.
Incoming payload -> Extract event type -> Match against source_events?
|
Yes -------+-------- No
| |
Process Return { ok: true, skipped: true }Setting Source Events
On Creation
curl -X POST https://api.aerostack.dev/api/smart-webhooks \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "github-pr-reviewer",
"workspace_id": "ws_abc123",
"instructions": "When a PR is opened or updated, review the changes...",
"source_type": "github",
"source_events": ["opened", "synchronize"]
}'On Update
curl -X PATCH https://api.aerostack.dev/api/smart-webhooks/swh_your_id \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_events": ["opened", "synchronize", "reopened"]
}'Accept All Events
Use ["*"] as a wildcard to accept all event types:
curl -X POST https://api.aerostack.dev/api/smart-webhooks \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "catch-all",
"workspace_id": "ws_abc123",
"instructions": "Log all incoming events and categorize them...",
"source_events": ["*"]
}'When source_events contains "*", all events are processed regardless of type.
No Filter (Accept All)
If source_events is not set or is an empty array, all events are accepted:
# No source_events field -- accepts everything
curl -X POST https://api.aerostack.dev/api/smart-webhooks \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "process-everything",
"workspace_id": "ws_abc123",
"instructions": "Handle all incoming webhooks..."
}'Omitting source_events and setting source_events: ["*"] have the same effect — all events are processed. Use ["*"] if you want to be explicit about the intent.
Skipped Event Response
When an event does not match the filter, the response includes a skipped flag:
{
"ok": true,
"skipped": true
}Skipped events are not logged in the run history and do not consume AI tokens.
Provider-Specific Event Types
GitHub
GitHub sends the event type in the action field of the payload. Common events to filter on:
| Event | Description |
|---|---|
opened | Issue or PR opened |
closed | Issue or PR closed |
synchronize | New commits pushed to a PR |
reopened | Issue or PR reopened |
labeled | Label added to issue/PR |
assigned | Issue/PR assigned |
created | Comment created |
submitted | Review submitted |
Example — PR review bot:
{
"source_events": ["opened", "synchronize", "reopened"]
}Example — Issue triager:
{
"source_events": ["opened"]
}Stripe
Stripe sends the event type in the type field at the top level. Common events:
| Event | Description |
|---|---|
payment_intent.succeeded | Payment completed |
payment_intent.payment_failed | Payment failed |
customer.subscription.created | New subscription |
customer.subscription.updated | Subscription changed |
customer.subscription.deleted | Subscription cancelled |
invoice.payment_succeeded | Invoice paid |
invoice.payment_failed | Invoice payment failed |
Example — Payment handler:
{
"source_events": [
"payment_intent.succeeded",
"payment_intent.payment_failed",
"customer.subscription.deleted"
]
}Shopify
Shopify uses the X-Shopify-Topic header and includes the event in the payload. Common topics:
| Event | Description |
|---|---|
orders/create | New order placed |
orders/fulfilled | Order fulfilled |
orders/cancelled | Order cancelled |
products/update | Product updated |
customers/create | New customer |
Example — Order processor:
{
"source_events": ["orders/create", "orders/fulfilled"]
}Custom Services
For your own services, include the event type in the payload using any of the supported fields:
// Using "type" field
{
"type": "user.created",
"data": { "id": "usr_123", "email": "[email protected]" }
}// Using "event_type" field
{
"event_type": "order.shipped",
"order_id": "ord_456"
}// Using "action" field
{
"action": "deployment.completed",
"environment": "production"
}Limits
| Constraint | Value |
|---|---|
Maximum events in source_events array | 50 |
| Maximum event string length | 100 characters |
Combining with HMAC Verification
Event filtering runs after HMAC signature verification. The processing order is:
- Signature verification (if
signing_secretis set) - Event type extraction from payload
- Event filter check against
source_events - AI execution (if event matches)
A request that passes signature verification but fails the event filter is not processed and returns { ok: true, skipped: true }. It is not logged in the run history.
Best Practices
Be specific with filters. Only process the events you need. This reduces AI token consumption and avoids unnecessary tool calls:
// Good -- specific events
{ "source_events": ["opened", "reopened"] }
// Avoid -- processes everything, wastes tokens on irrelevant events
{ "source_events": ["*"] }Match your instructions to your filters. If your source_events includes multiple event types, write instructions that handle each case:
Instructions:
- If the event is "opened": Classify and label the issue
- If the event is "reopened": Check if the original fix was reverted and comment with context
- If the event is "closed": Thank the contributor if it was fixed via PRUse dedicated webhooks for different workflows. Instead of one webhook that handles all GitHub events, create separate webhooks for issues, PRs, and deployments. This keeps instructions focused and reduces the chance of the AI misinterpreting the payload.