Skip to content
Learni
View all tutorials
AWS

How to Implement Amazon EventBridge with CDK in 2026

Lire en français

Introduction

Amazon EventBridge is AWS's serverless event bus that decouples sources (S3, API Gateway, SaaS) from targets (Lambda, Step Functions). In 2026, it excels in planetary-scale event-driven architectures, with <100ms latency and 100M+ events/second without provisioning. Unlike SQS/SNS, EventBridge features a native event schema registry and Pipes for real-time transformation and routing of event streams—ideal for microservices or IoT.

Why adopt it? Picture an e-commerce site: an S3 upload triggers an EventBridge rule filtering object.key for 'images/', routes to a resizing Lambda, then a Pipe enriches with DynamoDB data before Step Functions. This expert CDK tutorial deploys a full setup: custom bus, pattern-matching rule, Lambda target, schema registry, and filtering/transformation Pipe. Result: zero infrastructure maintenance and built-in CloudWatch monitoring. Ready to scale? (128 words)

Prerequisites

  • AWS account with IAM admin (or CDK roles)
  • AWS CLI v2 configured (aws configure)
  • Node.js 20+ and CDK 2.140+ (npm i -g aws-cdk)
  • Advanced knowledge of TypeScript, Lambda, and AWS events
  • us-east-1 region (modifiable in code)

Initialize the CDK project

terminal-init.sh
mkdir eventbridge-expert && cd eventbridge-expert
npm init -y
npm install aws-cdk-lib constructs
npm install -D @types/node ts-node cdk-nag typescript
cdk init app --language typescript
npm install @aws-cdk/aws-lambda-nodejs
mkdir lambda-handlers && touch lambda-handlers/index.ts
cdk context --clear

This script sets up a complete TypeScript CDK project, installs essential libs (CDK core, Lambda Node.js), and prepares directories. cdk-nag enables automatic security checks. Avoid yarn for CI/CD compatibility; test with cdk ls after init.

Understanding the base stack

We create a custom EventBus to isolate events from a SaaS partner (e.g., Stripe webhooks). A rule matches JSON patterns like {"detail-type": ["PaymentSucceeded"]}. The target is an asynchronous Node.js Lambda. Later: schema registry for validation and Pipes for enrichment.

Create the EventBus and Lambda target

lib/eventbridge-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import * as logs from 'aws-cdk-lib/aws-logs';

export class EventBridgeStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const eventBus = new events.EventBus(this, 'CustomEventBus', {
      eventBusName: 'ExpertEventBus',
    });

    const handler = new lambda.NodejsFunction(this, 'EventHandler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'index',
      entry: 'lambda-handlers/index.ts',
      logRetention: logs.RetentionDays.ONE_WEEK,
    });

    eventBus.grantPutEventsTo(handler);
  }
}

This stack defines a named EventBus and a Lambda handler with rotating logs. grantPutEventsTo allows the Lambda to emit events (useful for chaining). Pitfall: without a unique eventBusName, cross-account conflicts occur; use cdk-nag to check Least Privilege.

Implement the Lambda handler

lambda-handlers/index.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import { Context } from 'aws-lambda';

export const handler: APIGatewayProxyHandler = async (event: any, context: Context) => {
  console.log('Event reçu:', JSON.stringify(event, null, 2));

  const detail = event.detail || {};
  if (detail.type === 'PaymentSucceeded') {
    // Logique métier : update DynamoDB, notifier Slack
    console.log(`Paiement ID ${detail.paymentId} traité avec succès.`);
  }

  return {
    statusCode: 200,
    body: JSON.stringify({ message: 'Event processed', eventId: event.id }),
  };
};

Handler parses the standard EventBridge event (with id, detail). Real-world example: filters PaymentSucceeded for e-commerce. Add try/catch for Dead Letter Queue; test locally with sam local invoke before deployment.

Add a rule with pattern matching

EventBridge rules filter using JSONata-like patterns. Example: match only payments >$100 from a specific source. Associate with the Lambda via target.

Define the rule and target

lib/eventbridge-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import * as logs from 'aws-cdk-lib/aws-logs';

export class EventBridgeStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const eventBus = new events.EventBus(this, 'CustomEventBus', {
      eventBusName: 'ExpertEventBus',
    });

    const handler = new lambda.NodejsFunction(this, 'EventHandler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'index',
      entry: 'lambda-handlers/index.ts',
      logRetention: logs.RetentionDays.ONE_WEEK,
    });

    eventBus.grantPutEventsTo(handler);

    const rule = new events.Rule(this, 'PaymentRule', {
      eventBus,
      eventPattern: {
        source: ['stripe.com'],
        'detail-type': ['PaymentSucceeded'],
        detail: {
          amount: [{ numeric: ['>', 100] }],
        },
      },
    });

    rule.addTarget(new targets.LambdaFunction(handler, {
      event: events.RuleTargetInput.fromEventPath('$.detail'),
    }));
  }
}

Rule matches source: stripe.com, detail-type, and amount >100. fromEventPath passes $.detail to Lambda (input transformer). Advanced: patterns support exists, prefix. Avoid overly broad patterns (replay event costs).

Deploy the CDK stack

terminal-deploy.sh
cdk bootstrap aws://$(aws sts get-caller-identity --query Account --output text)/us-east-1
cdk synth
cdk deploy --require-approval never
aws events list-rules --event-bus-name ExpertEventBus --region us-east-1
aws lambda list-functions --function-version LATEST --region us-east-1

bootstrap prepares the CDK environment; synth/deploy pushes the stack. Verify rules/Lambdas via CLI. Pitfall: --require-approval never for CI; add --outputs-file stack.json for ARNs post-deploy.

Integrate schemas and Pipes

Schema Registry validates events at runtime (OpenAPI/JSON Schema). Pipes transform (Filter/Enrich) before the target, like a Stream API Gateway.

Add schema registry

event-schema.json
{
  "title": "PaymentSucceeded",
  "type": "object",
  "required": ["paymentId", "amount"],
  "properties": {
    "paymentId": { "type": "string" },
    "amount": { "type": "number", "minimum": 0 },
    "status": { "type": "string", "enum": ["succeeded", "failed"] }
  }
}

JSON Schema for validation. Import via Console/AWS CLI: aws eventbridge put-event-source-mapping. Associate with rule for auto-generated TypeScript codegen. Great for cross-team contracts.

Implement a transformation Pipe

lib/eventbridge-stack.ts
import * as pipes from 'aws-cdk-lib/aws-pipes';
// ... (code précédent +)

const pipe = new pipes.CfnPipe(this, 'PaymentPipe', {
  source: eventBus.eventBusArn,
  target: handler.functionArn,
  roleArn: pipeRole.roleArn,
  sourceParameters: {
    eventBridgeParameters: {
      source: eventBus.eventBusName,
    },
  },
  filterCriteria: {
    filter: {
      and_all: [
        { numeric: ['>', 100, '$.detail.amount'] },
        { prefix: ['pay_', '$.detail.paymentId'] },
      ],
    },
  },
});
pipe.node.addDependency(rule);

Pipe filters amount>100 AND paymentId prefix 'pay_', routes to Lambda. CfnPipe for 2026 features (CDK L2 in beta). Create pipeRole IAM beforehand. Advantage: built-in retry/backpressure vs. simple rules.

Test the event via CLI

terminal-test.sh
EVENT_DATA='{"Source":"stripe.com","DetailType":"PaymentSucceeded","Detail":{"paymentId":"pay_123","amount":150,"status":"succeeded"}}'
aws events put-events --entries "{\"EventBusName\":\"ExpertEventBus\",\"Source\":\"test\",\"DetailType\":\"PaymentSucceeded\",\"Detail\":\"$EVENT_DATA\"}" --region us-east-1
echo "Vérifiez CloudWatch Logs du handler Lambda"

Puts a mock event; matches the rule/Pipe. Detail is JSON-stringified. Scale with --entries array. Debug: aws logs filter-log-events --log-group-name /aws/lambda/EventHandler.

Best practices

  • IaC only: CDK/Terraform for versioning/replay; avoid Console for prod.
  • Precise patterns: Use numeric, exists for <1% false positives; test with Event Simulator.
  • Dead Letter Queue: Attach SQS to rules/Pipes for 100% delivery (max 185 retries).
  • Cross-account: Share buses via RAM; audit logs via EventBridge Audit.
  • Costs: $1/million events; archive >90d with replay.

Common errors to avoid

  • Pattern syntax: detail: { numeric: ['>', 100] } without $ path → no match; validate via Console simulator.
  • Permissions: Missing lambda:InvokeFunction on rule role → silent fail; use cdk-nag Suppress wisely.
  • Schema mismatch: Non-conforming event dropped without log; enable Event validation + DLQ.
  • Pipe loops: Target emits to same bus → infinite; break with source filter.

Next steps