---
cuppy_hub: aws
cuppy_path: guides/eventbridge/route-events.md
cuppy_section: docs
cuppy_doc: PVR3-DRQB
cuppy_doc_uuid: b7c2ba14-8847-44a6-b0cb-13222e664029
cuppy_permalink: https://cuppy.io/aws/d/PVR3-DRQB
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/eventbridge/route-events
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 7d8d8b66ddb3f92b8fa7254ba1897f6cdfccba90a991b53533fcd24db0658021
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Route events with EventBridge rules

**Most "EventBridge didn't work" reports come down to three things:** the pattern didn't match exactly, the target never granted EventBridge permission, or the rule lives in the wrong Region. Test the pattern against a real event, grant the target, and give every important target a DLQ.

## 1. Publish (custom events)

```ts
import { EventBridgeClient, PutEventsCommand, type PutEventsRequestEntry } from "@aws-sdk/client-eventbridge";

const eb = new EventBridgeClient({});

export async function publish(busName: string, detailType: string, details: object[]) {
  for (let i = 0; i < details.length; i += 10) {                     // max 10 entries per call
    let entries: PutEventsRequestEntry[] = details.slice(i, i + 10).map((d) => ({
      EventBusName: busName,
      Source: "com.example.orders",                                  // your own source; "aws." is for AWS services
      DetailType: detailType,
      Detail: JSON.stringify(d),                                     // must be a JSON object string
    }));
    for (let attempt = 0; entries.length && attempt < 3; attempt++) {
      const res = await eb.send(new PutEventsCommand({ Entries: entries }));
      if (!res.FailedEntryCount) { entries = []; break; }
      // Results are index-aligned with the request: keep only the failed ones.
      const failed = entries.filter((_, idx) => res.Entries?.[idx]?.ErrorCode);
      for (const [idx, r] of (res.Entries ?? []).entries())
        if (r.ErrorCode) console.warn("PutEvents entry failed", idx, r.ErrorCode, r.ErrorMessage);
      entries = failed;
    }
    if (entries.length) throw new Error(`${entries.length} events not accepted`);
  }
}
```

- **HTTP 200 doesn't mean every event was accepted.** Check `FailedEntryCount`, then each entry's `ErrorCode` and `ErrorMessage`.
- **Size:** the whole request must be under 1 MB. Event buses accept 1 MB payloads since 2026-01-29, except in Asia Pacific (New Zealand, Thailand, Malaysia, Taipei) and Mexico (Central). Whether every target accepts 1 MB isn't stated, so for large data put it in S3 and send the key.
- `Detail` integers must fit in a signed 64-bit integer, and nesting is limited to 1,000 levels.

## 2. Write and test the pattern

```ts
import { EventBridgeClient, TestEventPatternCommand } from "@aws-sdk/client-eventbridge";

const eb = new EventBridgeClient({});
const pattern = { source: ["com.example.orders"], "detail-type": ["OrderPlaced"], detail: { total: [{ numeric: [">", 100] }] } };
const sample = {
  id: "1", "detail-type": "OrderPlaced", source: "com.example.orders", account: "111111111111",
  time: "2026-10-18T12:00:00Z", region: "us-east-1", resources: [], detail: { total: 250 },
};
const { Result } = await eb.send(new TestEventPatternCommand({ EventPattern: JSON.stringify(pattern), Event: JSON.stringify(sample) }));
console.log("matches:", Result);
```

- Use a real event copied from logs or the console's sandbox as the sample.
- Matching is **exact and case-sensitive**: an ARN with `:` doesn't match one with `/`.
- CloudTrail-based "AWS API Call" events have no `resources` array, so don't match on it.
- Pattern syntax and limits: `reference/eventbridge/event-patterns.md`.

## 3. Create the rule in the right Region

- Rules see events on **their own bus in their own Region**.
- Events from **global services such as IAM and Route 53 are emitted only in `us-east-1`**, so a rule matching `iam.amazonaws.com` API calls must live there.
- Schedule rules run in **UTC**, within a minute of the scheduled time; cron resolution is one minute.

## 4. Give each target its permission

| Target | Permission EventBridge needs | Watch out |
|---|---|---|
| **Lambda** | Function resource policy: `aws lambda add-permission --function-name <fn> --statement-id <id> --action lambda:InvokeFunction --principal events.amazonaws.com --source-arn <rule-arn>` | **Don't add `SourceAccount`**: it stops invocation. For an alias or version, add `--qualifier` |
| **SQS** | Queue policy: `events.amazonaws.com`, `sqs:SendMessage`, `aws:SourceArn` = rule ARN | Encrypted queue: a customer managed key allowing `events.amazonaws.com` `kms:Decrypt` and `kms:GenerateDataKey` (not `aws/sqs`) |
| **SNS** | Topic policy: `events.amazonaws.com`, `sns:Publish` | Encrypted topic: the same key statement |
| Role-based targets (Step Functions, Kinesis, another bus, …) | An IAM role that trusts `events.amazonaws.com` and allows the target action | Regional STS endpoints must be enabled |

The console adds these permissions for you; `PutTargets` through the API, CLI or SDK does **not**. The CDK adds them when you use its target classes:

```ts
import { Duration, Stack } from "aws-cdk-lib";
import type { Construct } from "constructs";
import * as events from "aws-cdk-lib/aws-events";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as sqs from "aws-cdk-lib/aws-sqs";
import type * as lambda from "aws-cdk-lib/aws-lambda";

export function orderRule(scope: Construct, fn: lambda.IFunction) {
  const stack = Stack.of(scope);
  const bus = new events.EventBus(stack, "OrdersBus");
  const dlq = new sqs.Queue(stack, "OrdersRuleDlq", { retentionPeriod: Duration.days(14) });
  new events.Rule(stack, "BigOrders", {
    eventBus: bus,
    eventPattern: { source: ["com.example.orders"], detailType: ["OrderPlaced"] },
    targets: [new targets.LambdaFunction(fn, { deadLetterQueue: dlq, retryAttempts: 8, maxEventAge: Duration.hours(2) })],
  });
  return bus;
}
```

Synthesizing it produced:

- an `AWS::Lambda::Permission` for `events.amazonaws.com` with `SourceArn` set to the rule;
- a queue policy on the DLQ that allows `events.amazonaws.com` `sqs:SendMessage` with `aws:SourceArn` set to the rule;
- a target `RetryPolicy` with `MaximumRetryAttempts` 8 and `MaximumEventAgeInSeconds` 7200.

## 5. Retries, DLQ and monitoring

- **Default:** EventBridge retries for up to **24 hours and 185 attempts**, with exponential backoff and jitter, and then **drops the event**. Narrow this per target with `RetryPolicy` (`MaximumRetryAttempts`, `MaximumEventAgeInSeconds`).
- **Not retried:**
  - missing permissions, a deleted target, or a bad address go straight to the DLQ if there is one;
  - a throttling target may not be retried either.
- **DLQ:** must be a **standard** SQS queue in the same Region, with a queue policy for `events.amazonaws.com` (`aws:SourceArn` = rule ARN).
  - Each DLQ message carries the attributes `RULE_ARN`, `TARGET_ARN`, `ERROR_CODE` (for example `NO_PERMISSIONS`, `THROTTLING`, `TIMEOUT`), `ERROR_MESSAGE`, `EXHAUSTED_RETRY_CONDITION` and `RETRY_ATTEMPTS`.
- **Alarms:**
  - `FailedInvocations`, Sum ≥ 1 (AWS suggests 10 periods);
  - also `InvocationsSentToDLQ` and `InvocationsFailedToBeSentToDLQ`;
  - `TriggeredRules` versus `Invocations` shows match-but-no-delivery.
- Rarely, a target receives an event **more than once**, so make targets idempotent.
- **Loops:** a rule whose target changes the resource it watches can trigger itself. Match only the bad state.

Errors and the full troubleshooting table: `reference/eventbridge/errors.md`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
