← Back to ArticlesEvent Grid

Event Grid — Advanced Event Filtering and Routing Patterns

Implementing sophisticated event filtering, routing based on event content, and building complex event processing pipelines.

Event Grid — Advanced Event Filtering and Routing Patterns

Why Event Filtering Matters

Without filtering, every subscriber receives every event. This creates three problems:

  1. Cost — Event Grid charges per event delivery. If a subscriber only cares about 5% of events, you're paying 20x more than necessary.
  2. Performance — Subscribers waste compute cycles receiving, deserializing, and discarding irrelevant events. At scale, this adds up to real latency and resource pressure.
  3. Separation of concerns — When filtering happens inside subscriber code, every handler becomes coupled to the full event schema. Moving filters to the subscription level keeps handlers focused on business logic.

Event Grid's server-side filtering evaluates events before delivery. Events that don't match a subscription's filter are never sent — no delivery attempt, no charge, no wasted bandwidth.


Architecture: Filtering Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Event Grid Filtering Architecture                         │
└─────────────────────────────────────────────────────────────────────────────┘

┌──────────────────┐
│   Event Source   │   (e.g., Storage Account, Custom App, IoT Hub)
│                  │
│  Publishes ALL   │
│  events to topic │
└────────┬─────────┘
         │
         │  CloudEvents / Event Grid Schema
         ▼
┌──────────────────────────────────────────────────────────────────┐
│                        Event Grid Topic                          │
│                                                                  │
│   Incoming Event:                                                │
│   {                                                              │
│     "type": "OrderCreated",                                      │
│     "subject": "orders/us-west/12345",                           │
│     "data": { "amount": 5000, "region": "US", "priority": 1 }    │
│   }                                                              │
│                                                                  │
│   ┌────────────────────────────────────────────────────────┐     │
│   │              Filter Evaluation Engine                  │     │
│   │                                                        │     │
│   │  Sub 1 filter: subject beginsWith "orders/us-west"  ✓  │     │
│   │  Sub 2 filter: data.amount > 1000                   ✓  │     │
│   │  Sub 3 filter: type = "OrderCancelled"              ✗  │     │
│   │  Sub 4 filter: data.region in ["EU","APAC"]         ✗  │     │
│   └────────────────────────────────────────────────────────┘     │
└──────────┬───────────────┬───────────────┬───────────────┬───────┘
           │               │               │               │
           ▼               ▼               ▼               ▼
    ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
    │  Sub 1: ✓   │ │  Sub 2: ✓   │ │  Sub 3: ✗    │ │  Sub 4: ✗   │
    │  US-West    │ │  High-Value │ │  Cancellation│ │  EU/APAC    │
    │  Handler    │ │  Handler    │ │  Handler     │ │  Handler    │
    │             │ │             │ │  (no event)  │ │  (no event) │
    │  DELIVERED  │ │  DELIVERED  │ │  SKIPPED     │ │  SKIPPED    │
    └─────────────┘ └─────────────┘ └──────────────┘ └─────────────┘

Types of Filters

Event Grid supports three filter categories, which can be combined on a single subscription.

1. Event Type Filtering

The simplest filter — match on the eventType (or type in CloudEvents) field.

{
  "includedEventTypes": [
    "Microsoft.Storage.BlobCreated",
    "Microsoft.Storage.BlobDeleted"
  ]
}

Use this when a subscriber only handles specific event types from a source that emits many.

2. Subject Filtering

Match on the subject field using prefix and/or suffix:

OperatorUse CaseExample
subjectBeginsWithRoute by hierarchy/path"/orders/us-west" matches subjects starting with that path
subjectEndsWithRoute by file type or category".jpg" matches subjects ending in .jpg
{
  "subjectBeginsWith": "/orders/us-west",
  "subjectEndsWith": "/high-priority"
}

Both conditions must be true (AND logic). Subject filtering is case-sensitive by default — set isSubjectCaseSensitive: false to change this.

3. Advanced Filters

Advanced filters operate on any field in the event payload, including nested data.* properties. You can have up to 25 advanced filters per subscription, each with up to 25 values.

OperatorData TypeDescription
NumberGreaterThanNumericValue > threshold
NumberGreaterThanOrEqualsNumericValue >= threshold
NumberLessThanNumericValue < threshold
NumberLessThanOrEqualsNumericValue <= threshold
NumberInNumericValue is in the specified set
NumberNotInNumericValue is NOT in the specified set
NumberInRangeNumericValue falls within a range
NumberNotInRangeNumericValue falls outside a range
StringContainsStringValue contains any of the specified strings
StringNotContainsStringValue does NOT contain any of the specified strings
StringBeginsWithStringValue starts with any of the specified strings
StringNotBeginsWithStringValue does NOT start with any of the specified strings
StringEndsWithStringValue ends with any of the specified strings
StringNotEndsWithStringValue does NOT end with any of the specified strings
StringInStringExact match against a set of values
StringNotInStringValue is NOT in the specified set
BoolEqualsBooleanValue equals true or false
IsNullOrUndefinedAnyField is null or missing
IsNotNullAnyField exists and is not null

Key rule: Multiple advanced filters use AND logic — all must match for the event to be delivered.


Step-by-Step Implementation

Prerequisites

# Ensure Event Grid extension is registered
az provider register --namespace Microsoft.EventGrid

# Create resource group and topic
az group create --name rg-eventgrid-demo --location eastus2

az eventgrid topic create \
  --name orders-topic \
  --resource-group rg-eventgrid-demo \
  --location eastus2 \
  --input-schema cloudeventschemav1_0

Creating Subscriptions with Subject Filters

# Route US-West orders to a specific webhook
az eventgrid event-subscription create \
  --name us-west-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://us-west-handler.azurewebsites.net/api/orders" \
  --subject-begins-with "/orders/us-west" \
  --subject-ends-with "/new"

Creating Subscriptions with Event Type Filters

# Only receive OrderCreated and OrderUpdated events
az eventgrid event-subscription create \
  --name order-lifecycle \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://lifecycle-handler.azurewebsites.net/api/events" \
  --included-event-types "OrderCreated" "OrderUpdated"

Creating Subscriptions with Advanced Filters

# High-value orders (amount > 1000) from US or EU regions
az eventgrid event-subscription create \
  --name high-value-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://high-value-handler.azurewebsites.net/api/orders" \
  --included-event-types "OrderCreated" \
  --advanced-filter data.amount NumberGreaterThan 1000 \
  --advanced-filter data.region StringIn US EU

C# Implementation: Creating Filtered Subscriptions Programmatically

using Azure;
using Azure.Messaging.EventGrid.Namespaces;
using Azure.ResourceManager;
using Azure.ResourceManager.EventGrid;
using Azure.ResourceManager.EventGrid.Models;

public class EventGridFilterService
{
    private readonly ArmClient _armClient;

    public EventGridFilterService(ArmClient armClient)
    {
        _armClient = armClient;
    }

    public async Task CreateHighValueOrderSubscriptionAsync(string topicResourceId)
    {
        var topicResource = _armClient.GetEventGridTopicResource(
            new ResourceIdentifier(topicResourceId));

        var subscriptionData = new EventGridSubscriptionData
        {
            Destination = new WebHookEventSubscriptionDestination
            {
                Endpoint = new Uri("https://high-value-handler.azurewebsites.net/api/orders")
            },
            Filter = new EventSubscriptionFilter
            {
                SubjectBeginsWith = "/orders/",
                IncludedEventTypes = { "OrderCreated", "OrderUpdated" },
                AdvancedFilters =
                {
                    new NumberGreaterThanAdvancedFilter("data.amount") { Value = 1000 },
                    new StringInAdvancedFilter("data.region") { Values = { "US", "EU" } },
                    new BoolEqualsAdvancedFilter("data.isVerified") { Value = true }
                }
            },
            RetryPolicy = new EventSubscriptionRetryPolicy
            {
                MaxDeliveryAttempts = 30,
                EventTimeToLiveInMinutes = 1440
            }
        };

        await topicResource.GetEventGridTopicEventSubscriptions()
            .CreateOrUpdateAsync(WaitUntil.Completed, "high-value-orders", subscriptionData);
    }
}

C# Event Handler with Filter Validation

using Azure.Messaging;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/orders")]
public class OrderEventsController : ControllerBase
{
    private readonly ILogger<OrderEventsController> _logger;

    public OrderEventsController(ILogger<OrderEventsController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    public async Task<IActionResult> HandleOrderEvent([FromBody] CloudEvent[] events)
    {
        foreach (var cloudEvent in events)
        {
            _logger.LogInformation(
                "Received {Type} for subject {Subject}",
                cloudEvent.Type, cloudEvent.Subject);

            var orderData = cloudEvent.Data.ToObjectFromJson<OrderEventData>();

            // Event Grid already filtered — we know amount > 1000 and region is US/EU
            await ProcessHighValueOrder(orderData);
        }

        return Ok();
    }

    private Task ProcessHighValueOrder(OrderEventData order)
    {
        _logger.LogInformation(
            "Processing high-value order {OrderId}: ${Amount} in {Region}",
            order.OrderId, order.Amount, order.Region);
        // Business logic here
        return Task.CompletedTask;
    }
}

public record OrderEventData(string OrderId, decimal Amount, string Region, bool IsVerified);

Complex Filter Combinations

AND Logic (Default Behavior)

All filters on a subscription are combined with AND. Every condition must be true:

# Event must match ALL of these:
# - Subject starts with /orders/
# - Event type is OrderCreated
# - data.amount > 500
# - data.region is "US" or "EU"
# - data.isVerified is true
az eventgrid event-subscription create \
  --name strict-filter-sub \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://handler.azurewebsites.net/api/events" \
  --subject-begins-with "/orders/" \
  --included-event-types "OrderCreated" \
  --advanced-filter data.amount NumberGreaterThan 500 \
  --advanced-filter data.region StringIn US EU \
  --advanced-filter data.isVerified BoolEquals true

Achieving OR Logic

Event Grid doesn't natively support OR between filters. Use one of these patterns:

Pattern 1: Multiple subscriptions pointing to the same endpoint

# OR: amount > 5000 OR priority = "critical"
# Subscription A: high amount
az eventgrid event-subscription create \
  --name high-amount-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://priority-handler.azurewebsites.net/api/events" \
  --advanced-filter data.amount NumberGreaterThan 5000

# Subscription B: critical priority
az eventgrid event-subscription create \
  --name critical-priority-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://priority-handler.azurewebsites.net/api/events" \
  --advanced-filter data.priority StringIn critical

Note: The handler may receive duplicates if an event matches both subscriptions. Implement idempotency using the event ID.

Pattern 2: StringIn / NumberIn for OR within a single field

# data.region is "US" OR "EU" OR "APAC" — this is native OR within one filter
--advanced-filter data.region StringIn US EU APAC

Content-Based Routing Patterns

Route events to different endpoints based on event content without subscriber-side logic:

# Route by order value tier
# Tier 1: Premium orders (> $10,000) → dedicated handler
az eventgrid event-subscription create \
  --name premium-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://premium-handler.azurewebsites.net/api/orders" \
  --advanced-filter data.amount NumberGreaterThan 10000

# Tier 2: Standard orders ($100–$10,000) → standard handler
az eventgrid event-subscription create \
  --name standard-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://standard-handler.azurewebsites.net/api/orders" \
  --advanced-filter data.amount NumberGreaterThan 100 \
  --advanced-filter data.amount NumberLessThanOrEquals 10000

# Tier 3: Micro orders (≤ $100) → batch processor
az eventgrid event-subscription create \
  --name micro-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://batch-handler.azurewebsites.net/api/orders" \
  --advanced-filter data.amount NumberLessThanOrEquals 100

Routing to Different Destination Types

// Create subscriptions routing to different Azure services
public async Task SetupContentBasedRoutingAsync(string topicResourceId)
{
    var topic = _armClient.GetEventGridTopicResource(new ResourceIdentifier(topicResourceId));
    var subscriptions = topic.GetEventGridTopicEventSubscriptions();

    // High-priority → Service Bus queue for guaranteed processing
    await subscriptions.CreateOrUpdateAsync(WaitUntil.Completed, "high-priority-to-servicebus",
        new EventGridSubscriptionData
        {
            Destination = new ServiceBusQueueEventSubscriptionDestination
            {
                ResourceId = new ResourceIdentifier("/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.ServiceBus/namespaces/my-ns/queues/high-priority")
            },
            Filter = new EventSubscriptionFilter
            {
                AdvancedFilters = { new StringInAdvancedFilter("data.priority") { Values = { "critical", "high" } } }
            }
        });

    // Analytics events → Event Hub for stream processing
    await subscriptions.CreateOrUpdateAsync(WaitUntil.Completed, "analytics-to-eventhub",
        new EventGridSubscriptionData
        {
            Destination = new EventHubEventSubscriptionDestination
            {
                ResourceId = new ResourceIdentifier("/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventHub/namespaces/my-ns/eventhubs/analytics")
            },
            Filter = new EventSubscriptionFilter
            {
                IncludedEventTypes = { "PageViewed", "ButtonClicked", "SessionStarted" }
            }
        });
}

Dead Letter Handling

Events that fail delivery after all retry attempts — or events that can't be delivered due to configuration errors — go to a dead letter destination.

# Create a storage container for dead letters
az storage container create \
  --name deadletters \
  --account-name mystorageaccount

# Create subscription with dead lettering enabled
az eventgrid event-subscription create \
  --name orders-with-deadletter \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --endpoint "https://handler.azurewebsites.net/api/orders" \
  --advanced-filter data.amount NumberGreaterThan 1000 \
  --deadletter-endpoint "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.Storage/storageAccounts/mystorageaccount/blobServices/default/containers/deadletters" \
  --max-delivery-attempts 10 \
  --event-ttl 1440

Processing Dead Letters

using Azure.Storage.Blobs;

public class DeadLetterProcessor
{
    private readonly BlobContainerClient _containerClient;
    private readonly ILogger<DeadLetterProcessor> _logger;

    public DeadLetterProcessor(BlobServiceClient blobClient, ILogger<DeadLetterProcessor> logger)
    {
        _containerClient = blobClient.GetBlobContainerClient("deadletters");
        _logger = logger;
    }

    public async Task ProcessDeadLettersAsync()
    {
        await foreach (var blob in _containerClient.GetBlobsAsync())
        {
            var blobClient = _containerClient.GetBlobClient(blob.Name);
            var content = await blobClient.DownloadContentAsync();
            var deadLetter = content.Value.Content.ToObjectFromJson<DeadLetterEvent>();

            _logger.LogWarning(
                "Dead letter: {EventType} failed with {Error}. Last attempt: {LastAttempt}",
                deadLetter.EventType,
                deadLetter.DeadLetterReason,
                deadLetter.LastDeliveryAttemptTime);

            // Retry, alert, or archive based on reason
            if (deadLetter.DeadLetterReason == "MaxDeliveryAttemptsExceeded")
                await RetryWithBackoff(deadLetter);
        }
    }

    private Task RetryWithBackoff(DeadLetterEvent evt) => Task.CompletedTask;
}

public record DeadLetterEvent(string EventType, string DeadLetterReason, DateTime LastDeliveryAttemptTime);

Real-World Scenarios

Scenario 1: E-Commerce Order Routing by Value and Region

# Premium US orders → white-glove fulfillment team
az eventgrid event-subscription create \
  --name premium-us-fulfillment \
  --source-resource-id $TOPIC_ID \
  --endpoint "https://premium-fulfillment.azurewebsites.net/api/orders" \
  --subject-begins-with "/orders/us" \
  --advanced-filter data.orderTotal NumberGreaterThan 5000 \
  --advanced-filter data.customerTier StringIn platinum gold

# EU orders → GDPR-compliant handler in EU region
az eventgrid event-subscription create \
  --name eu-gdpr-handler \
  --source-resource-id $TOPIC_ID \
  --endpoint "https://eu-handler.azurewebsites.net/api/orders" \
  --advanced-filter data.region StringIn EU-West EU-North EU-Central \
  --advanced-filter data.containsPII BoolEquals true

Scenario 2: IoT Device Filtering by Type and Severity

# Critical temperature alerts from industrial sensors
az eventgrid event-subscription create \
  --name critical-temp-alerts \
  --source-resource-id $TOPIC_ID \
  --endpoint "https://alert-handler.azurewebsites.net/api/iot" \
  --included-event-types "TemperatureReading" \
  --advanced-filter data.severity StringIn critical \
  --advanced-filter data.deviceType StringIn industrial-sensor \
  --advanced-filter data.temperature NumberGreaterThan 85

# Battery-low events for all mobile devices
az eventgrid event-subscription create \
  --name battery-monitoring \
  --source-resource-id $TOPIC_ID \
  --endpoint "https://device-mgmt.azurewebsites.net/api/battery" \
  --included-event-types "BatteryStatus" \
  --advanced-filter data.batteryPercent NumberLessThan 20 \
  --advanced-filter data.deviceType StringIn mobile tablet wearable

Scenario 3: Multi-Tenant Event Routing

# Route events to tenant-specific handlers
az eventgrid event-subscription create \
  --name tenant-acme-corp \
  --source-resource-id $TOPIC_ID \
  --endpoint "https://acme-handler.azurewebsites.net/api/events" \
  --advanced-filter data.tenantId StringIn tenant-acme-001

az eventgrid event-subscription create \
  --name tenant-globex \
  --source-resource-id $TOPIC_ID \
  --endpoint "https://globex-handler.azurewebsites.net/api/events" \
  --advanced-filter data.tenantId StringIn tenant-globex-002

Testing Filters

Publish Test Events with Azure CLI

# Get topic endpoint and key
TOPIC_ENDPOINT=$(az eventgrid topic show --name orders-topic -g rg-eventgrid-demo --query "endpoint" -o tsv)
TOPIC_KEY=$(az eventgrid topic key list --name orders-topic -g rg-eventgrid-demo --query "key1" -o tsv)

# Publish a test event that should match high-value filter
curl -X POST "$TOPIC_ENDPOINT" \
  -H "aeg-sas-key: $TOPIC_KEY" \
  -H "Content-Type: application/cloudevents+json" \
  -d '{
    "specversion": "1.0",
    "type": "OrderCreated",
    "source": "/orders/us-west",
    "id": "test-001",
    "subject": "/orders/us-west/12345",
    "data": {
      "orderId": "12345",
      "amount": 5500,
      "region": "US",
      "isVerified": true,
      "priority": "high"
    }
  }'

# Publish an event that should NOT match (amount too low)
curl -X POST "$TOPIC_ENDPOINT" \
  -H "aeg-sas-key: $TOPIC_KEY" \
  -H "Content-Type: application/cloudevents+json" \
  -d '{
    "specversion": "1.0",
    "type": "OrderCreated",
    "source": "/orders/us-west",
    "id": "test-002",
    "subject": "/orders/us-west/67890",
    "data": {
      "orderId": "67890",
      "amount": 50,
      "region": "US",
      "isVerified": true,
      "priority": "low"
    }
  }'

Verify Delivery with CLI

# Check subscription delivery metrics
az eventgrid event-subscription show \
  --name high-value-orders \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/orders-topic" \
  --include-full-endpoint-url \
  --query "{deliveredEvents: provisioningState, endpoint: destination.endpointUrl}"

Monitoring with KQL Queries

Track Filtered vs Delivered Events

// Events published vs delivered per subscription (last 24h)
AegDeliverySuccessCount
| where TimeGenerated > ago(24h)
| summarize DeliveredCount = sum(Count) by EventSubscriptionName
| join kind=leftouter (
    AegPublishSuccessCount
    | where TimeGenerated > ago(24h)
    | summarize PublishedCount = sum(Count) by TopicName
) on $left.EventSubscriptionName == $right.TopicName
| project EventSubscriptionName, DeliveredCount, PublishedCount,
    FilterEfficiency = round(100.0 - (DeliveredCount * 100.0 / PublishedCount), 2)

Detect Filter Misconfigurations

// Subscriptions receiving zero events (possible misconfigured filter)
AegDeliverySuccessCount
| where TimeGenerated > ago(7d)
| summarize TotalDelivered = sum(Count) by EventSubscriptionName
| where TotalDelivered == 0
| project EventSubscriptionName, TotalDelivered,
    Status = "⚠️ No events delivered in 7 days — check filter configuration"

Dead Letter Monitoring

// Dead letter events by reason
AegDeadLetterCount
| where TimeGenerated > ago(24h)
| summarize DeadLetterCount = sum(Count) by EventSubscriptionName, DeadLetterReason
| order by DeadLetterCount desc
| project EventSubscriptionName, DeadLetterReason, DeadLetterCount,
    Severity = iff(DeadLetterCount > 100, "🔴 Critical", iff(DeadLetterCount > 10, "🟡 Warning", "🟢 Normal"))

Best Practices

PracticeWhy
Filter at the subscription level, not in handler codeReduces cost, latency, and handler complexity
Use subject hierarchy (e.g., /orders/{region}/{id})Enables efficient prefix/suffix filtering
Keep advanced filters under 5 per subscriptionComplex filters add evaluation latency
Use StringIn over multiple StringContainsMore efficient for exact-match scenarios
Enable dead lettering on all production subscriptionsPrevents silent event loss
Set appropriate event-ttl and max-delivery-attemptsBalance between reliability and stale event delivery
Use CloudEvents schema for new topicsBetter interoperability and richer filtering on source
Test filters with known events before productionCatch logic errors early
Monitor filter efficiency with KQLIdentify over-broad or over-narrow filters
Implement idempotent handlersRequired when using OR-pattern with multiple subscriptions

Common Pitfalls

1. Assuming OR logic between advanced filters All advanced filters are AND. An event must match every filter. Use multiple subscriptions or StringIn/NumberIn for OR semantics.

2. Case sensitivity in subject filters Subject filters are case-sensitive by default. "/Orders/US" won't match "/orders/us". Set isSubjectCaseSensitive: false if needed.

3. Filtering on missing fields If an event doesn't have the field referenced in an advanced filter, the filter evaluates to false — the event is silently dropped. Use IsNotNull to guard against this.

4. Exceeding filter limits Maximum 25 advanced filters per subscription, 25 values per filter. Exceeding these returns a validation error at creation time.

5. Not enabling dead lettering Without dead lettering, events that exhaust retries are permanently lost. Always configure a dead letter destination for production workloads.

6. Overlapping filters delivering duplicates When using multiple subscriptions to the same endpoint (OR pattern), the handler receives the event multiple times. Track event IDs to deduplicate.

7. Using NumberGreaterThan on string fields Type mismatches cause the filter to never match. Ensure the event payload field type matches the filter operator type.