Node.js Serverless on AWS Lambda · Event-Driven Lambda
EventBridge: Patterns, Retries and Archives
Why some events silently never reach their target, and how to make EventBridge delivery observable and recoverable.
Scenario
Example app: services publish domain events (OrderPlaced, OrderShipped) to a custom EventBridge bus. A rule routes high-value orders to a Lambda function that notifies the fraud team.
What you see in production
The fraud team notices that some high-value orders never triggered a notification. There are no errors in the function's logs, because the function was never invoked for those events.
The naive code
{
"source": ["orders"],
"detail-type": ["orderPlaced"],
"detail": { "total": [{ "numeric": [">", 1000] }] }
}
The publisher sends "detail-type": "OrderPlaced", and some services send total as a string ("1250.00"). The rule's target has no dead-letter queue.
Reproduce it
A small matcher with EventBridge's semantics: values are case-sensitive, arrays mean "any of", and numeric matching only applies to JSON numbers:
function matches(pattern, event) {
return Object.entries(pattern).every(([key, expected]) => {
const value = event[key];
if (Array.isArray(expected)) {
return expected.some((e) => {
if (e && typeof e === "object" && "numeric" in e) {
const [op, n] = e.numeric;
return typeof value === "number" && (op === ">" ? value > n : value < n);
}
return e === value;
});
}
return value && typeof value === "object" && matches(expected, value);
});
}
const naiveRule = { source: ["orders"], "detail-type": ["orderPlaced"], detail: { total: [{ numeric: [">", 1000] }] } };
const fixedRule = { source: ["orders"], "detail-type": ["OrderPlaced"], detail: { total: [{ numeric: [">", 1000] }] } };
const events = [
{ source: "orders", "detail-type": "OrderPlaced", detail: { id: "o1", total: 1250 } },
{ source: "orders", "detail-type": "OrderPlaced", detail: { id: "o2", total: "1250.00" } },
{ source: "orders", "detail-type": "OrderPlaced", detail: { id: "o3", total: 90 } },
];
for (const e of events) {
console.log(e.detail.id, "naive:", matches(naiveRule, e), "| fixed:", matches(fixedRule, e));
}
Output
o1 naive: false | fixed: true o2 naive: false | fixed: false o3 naive: false | fixed: false
Root cause
EventBridge matching is exact and type-sensitive: detail-type values are case-sensitive strings, and numeric filters only match JSON numbers. A non-matching event is not an error; it simply goes nowhere. When a target does fail (throttling, function errors), EventBridge retries with backoff for up to 24 hours by default, and then drops the event unless the target has a dead-letter queue.
The fix
- Treat events as a contract: publish a schema (the EventBridge schema registry, or JSON Schema in a shared package), validate events before publishing, and use consistent naming and types.
- Test rules against sample events in CI with the
TestEventPatternAPI (or the console's sandbox). - Configure every target with a DLQ and a deliberate retry policy (maximum event age and retry attempts), and alarm on the DLQ.
- Enable an archive on the bus so events can be replayed after a bug fix.
# AWS SAM
FraudNotifier:
Type: AWS::Serverless::Function
Properties:
Events:
HighValue:
Type: EventBridgeRule
Properties:
EventBusName: !Ref OrdersBus
Pattern: { source: [orders], detail-type: [OrderPlaced], detail: { total: [{ numeric: [">", 1000] }] } }
DeadLetterConfig: { Arn: !GetAtt FraudDlq.Arn }
RetryPolicy: { MaximumEventAgeInSeconds: 3600, MaximumRetryAttempts: 10 }
Trade-offs
- Strict schemas slow down change; version events (
OrderPlaced.v2or a version field) instead of changing meaning in place. - Archives cost storage; set retention to what you need for recovery.
- EventBridge adds some latency compared with direct invocation; it buys decoupling and fan-out.
Detect it in production
- Per-rule metrics:
MatchedEvents,Invocations,FailedInvocations,InvocationsSentToDlq. - Compare published counts (from the publisher) with
MatchedEventsfor important rules. - Alarms on DLQ depth and on rules that suddenly match zero events.
Key points
- Pattern matching is exact and type-sensitive; non-matches are silent.
- Validate events against a schema and test patterns in CI.
- DLQs and retry policies on every target; archive and replay for recovery.
Further reading: Amazon EventBridge event patterns · EventBridge retry policy and DLQs
Practice questions
An EventBridge rule never fires for some events, and the function logs show nothing. What do you check?
Answer
Whether the events actually match the pattern: case-sensitive detail-type and source values, field names and types (numeric filters need JSON numbers). Test the pattern against real sample events with TestEventPattern and compare MatchedEvents with publish counts.
What happens to an event if its Lambda target keeps failing?
Answer
EventBridge retries with exponential backoff up to the retry policy limits (by default up to 24 hours and 185 attempts), then drops the event unless the target has a dead-letter queue configured.
How do archives and replay help after a bug in a consumer?
Answer
An archive keeps a copy of events published to the bus; after fixing the consumer you can replay events from the affected time window to reprocess them, provided consumers are idempotent.
Open in the app to mark lessons complete, take notes and track your progress.