skip to content

How does an EventBridge rule's event pattern decide whether an event matches, and how would you debug a rule that never fires?

level: middleimportance: must knowfreq 72%

answer

  1. a template shaped like the event
  2. fields AND, array values OR
  3. omitted fields are ignored
  4. exact and case-sensitive by default
  5. was it a match failure or a delivery failure?

basics

~20 s

An event pattern mirrors the event's JSON structure: every field named in the pattern must match, values inside an array are alternatives, and fields the pattern omits are ignored. String matches are exact and case-sensitive unless you use a content filter such as prefix or wildcard.

solid answer

~50 s

Think of the pattern as a subset of the event with the leaf values replaced by arrays of candidates. Fields you list are ANDed; values within one array are ORed; anything you do not mention is ignored. Matching a string is exact and case-sensitive by default — `"source": ["com.acme"]` will not match `com.acme.orders`. For anything looser you use a content filter object: `prefix`, `suffix`, `wildcard`, `anything-but`, `numeric`, `cidr`, `exists`, `equals-ignore-case`, and `$or` to combine alternatives across fields. When a rule never fires the cause is almost always shape, not logic: matching `detailType` instead of the delivered `detail-type`, using a bare string where an array is required, or nesting the pattern wrongly under `detail`. Debug it by feeding a real event and the pattern to the `TestEventPattern` API, and by checking the rule's `TriggeredRules` metric — if that is non-zero the pattern is fine and your problem is delivery, not matching.

code

json · 8 lines
json
{
  "source": [{ "prefix": "com.acme." }],
  "detail-type": ["OrderPlaced"],
  "detail": {
    "total": [{ "numeric": [">=", 100] }],
    "couponCode": [{ "exists": true }]
  }
}

go deeper

for a junior

Know that a pattern looks like the event with array values, that listed fields must all match, and that fields you leave out are ignored. Say that matching is exact by default.

for a middle

Explain AND across fields versus OR within an array, name a few content filters such as prefix, numeric and exists, and describe the classic shape bugs that make a rule never fire.

for a senior

Demonstrate a debugging method: separate matching from delivery using TriggeredRules and FailedInvocations, confirm with TestEventPattern, and fall back to a broad catch-all rule logging real events.

for a principal

Treat patterns as coupling between producer and consumer: version the payload, test patterns against captured fixtures in CI, and keep them minimal so a producer change does not silently break a dozen subscribers.

## The matching model An event pattern is not a query language. It is a **template shaped like the event**, and EventBridge walks the two documents together. ```json { "source": ["com.acme.orders"], "detail-type": ["OrderPlaced", "OrderCancelled"], "detail": { "total": [{ "numeric": [">", 100] }], "region": [{ "anything-but": ["test"] }] } } ``` The rules that govern it are few and worth memorising: 1. **Structure must mirror the event.** To match a field inside the payload you nest it under `detail`, exactly as it appears in the delivered envelope. 2. **Every field present in the pattern must match.** Fields are combined with AND. 3. **Values are always arrays**, and the values inside one array are alternatives (OR). A bare string is a syntax error, not a shortcut. 4. **Fields absent from the pattern are ignored.** An empty-ish pattern like `{"source": ["com.acme.orders"]}` matches every event from that source regardless of its other content — which is exactly how you build a catch-all. 5. **String comparison is exact and case-sensitive** unless you use a content filter. ## Content filters When exact equality is not enough, the array element becomes an object naming a comparison: - `prefix` and `suffix` — `{"prefix": "com.acme."}` matches a family of sources. - `wildcard` — `*` segments inside a string. - `anything-but` — negation, over a value or a list; it can itself wrap a `prefix`. - `numeric` — comparison operators and ranges, e.g. `[">", 100, "<=", 500]`, and only on JSON numbers, not numeric strings. - `cidr` — IP-range matching, useful on security events. - `exists` — `{"exists": true}` or `false`, to branch on presence of an optional field. - `equals-ignore-case` — case-insensitive equality. - `$or` — alternatives spanning *different* fields, which the plain array form cannot express because arrays only OR within a single field. ## Why a rule silently does nothing EventBridge never tells a publisher that nothing matched, and a rule that matches nothing produces no error anywhere. The realistic causes, roughly in order of frequency: - **Wrong field names.** The publisher sends `Source`/`DetailType`, but the pattern must use the delivered names `source`/`detail-type`. Writing `detailType` in a pattern is the single most common bug. - **Wrong bus.** The rule was created on `default` while the producer omitted `EventBusName`… or the reverse. Rules only see events on their own bus. - **Scalar instead of array.** `"source": "com.acme.orders"` is invalid; it must be `["com.acme.orders"]`. - **Assuming prefix semantics.** Exact match is the default; a family of sources needs an explicit `prefix` filter. - **Numbers as strings.** `numeric` will not match `"total": "150"`; the payload has to carry a real JSON number. - **Over-specification.** Every extra field ANDed in is another chance to miss. Patterns should assert the minimum that identifies the event. ## How to debug it methodically Start by separating *matching* from *delivery*, because they fail differently and are fixed differently: - `TestEventPattern` takes a pattern and a complete event and answers true/false with no infrastructure involved. Note that the event you pass must be a full envelope — `id`, `source`, `detail-type`, `account`, `time`, `region`, `resources`, `detail` — so this also forces you to look at the real shape. - The rule's **`TriggeredRules`** metric in CloudWatch tells you whether the pattern matched at all. Zero means a matching problem. Non-zero means matching is fine and you should be looking at `FailedInvocations` and the target's permissions instead. - A **catch-all debugging rule** is the pragmatic move on a live system: create a temporary rule with a very broad pattern (say just the `source`) targeting a CloudWatch Logs group, then read the real events as they arrive and compare them field by field with your pattern. Nine times out of ten the mismatch is visible in the first event you read. A disciplined habit that prevents most of this: capture one real event, save it as a fixture, and assert your pattern against it with `TestEventPattern` in CI. Patterns are code and they rot when the producer's payload changes; nothing else in the system will tell you when that happens.

  • How do you express "detail-type is OrderPlaced OR the source is com.acme.audit" in one pattern?
    The plain array form only ORs values within a single field, so you need the `$or` operator, whose value is an array of sub-patterns each asserting its own fields. Everything outside the `$or` still ANDs with it. Reach for it sparingly — two rules with simpler patterns are often easier to read, and rules are cheap.
  • A pattern uses numeric on detail.total but never matches, even though totals are clearly above the threshold. Why?
    Almost certainly the producer serialises the amount as a string. The `numeric` filter only applies to JSON numbers, so `"total": "150"` never satisfies `{"numeric": [">", 100]}` and fails silently. Fix it in the producer rather than the pattern; monetary values sent as strings will bite every other consumer too.
  • The rule's TriggeredRules metric is climbing but the target sees nothing. What is your next step?
    That splits the problem cleanly: matching works, delivery does not. Look at `FailedInvocations` for the rule and at the target's permissions — a missing resource policy on a Lambda or an IAM role that cannot write to the queue is the usual cause. Add a dead-letter queue to the target so the failures stop being invisible.
  • Why should a pattern assert as few fields as possible?
    Because every field is ANDed, so each one is another way to stop matching when the producer's payload evolves. Assert what identifies the event — usually `source` and `detail-type` plus one discriminator — and do the finer filtering in the consumer, where a mismatch produces a log line instead of silence.

saying these in an interview costs you the question

  • Writing detailType instead of detail-type in the pattern
  • Using a bare string where the pattern requires an array
  • Assuming string matching is prefix-based or case-insensitive
  • Expecting an error or alert when nothing matches
  • Applying numeric filters to values sent as strings

context