Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions appsync-events-lambda-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# AWS AppSync Events with AWS Lambda

This pattern deploys an AWS AppSync Events API for real-time WebSocket pub/sub with an AWS Lambda event handler.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/appsync-events-lambda-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details.

## Requirements

* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Node.js 22+](https://nodejs.org/en/download/) installed
* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed

## Architecture

![Architecture Diagram](architecture.svg)

## How it works

1. Publishers send events via HTTP POST to the AWS AppSync Events endpoint.
2. The AWS Lambda function processes and enriches events before delivery.
3. AWS AppSync Events delivers messages to all WebSocket subscribers on that channel.
4. Channel namespaces (`notifications`, `alerts`) organize topics.

## Deployment

1. Clone the repository and navigate to the pattern directory:
```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/appsync-events-lambda-cdk
```

2. Install dependencies:
```bash
npm install
```

3. Bootstrap CDK (one-time per account/region):
```bash
cdk bootstrap
```

4. Deploy the stack:
```bash
cdk deploy
```

## Testing

```bash
# Publish an event (replace values from cdk deploy output)
curl -X POST "https://<HttpEndpoint>/event" \
-H "x-api-key: <ApiKeyValue>" \
-H "Content-Type: application/json" \
-d '{"channel":"notifications/general","events":["{\"message\":\"Hello from CDK\"}"]}'
```

## Cleanup

```bash
cdk destroy
```
23 changes: 23 additions & 0 deletions appsync-events-lambda-cdk/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions appsync-events-lambda-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AppsyncEventsLambdaStack } from '../lib/appsync-events-lambda-stack';
const app = new cdk.App();
new AppsyncEventsLambdaStack(app, 'AppsyncEventsLambdaStack');
1 change: 1 addition & 0 deletions appsync-events-lambda-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"app":"npx ts-node --prefer-ts-exts bin/app.ts"}
70 changes: 70 additions & 0 deletions appsync-events-lambda-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"title": "AWS AppSync Events with AWS Lambda handler",
"description": "Deploy an AWS AppSync Events API with an AWS Lambda handler for real-time pub/sub message processing.",
"language": "TypeScript",
"level": "300",
"framework": "CDK",
"introBox": {
"headline": "How it works",
"text": [
"AWS AppSync Events provides WebSocket-based real-time pub/sub. An AWS Lambda handler processes published messages before delivery to subscribers."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/appsync-events-lambda-cdk",
"templateURL": "serverless-patterns/appsync-events-lambda-cdk",
"projectFolder": "appsync-events-lambda-cdk"
}
},
"resources": {
"bullets": [
{
"text": "AWS AppSync Events",
"link": "https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html"
}
]
},
"deploy": {
"text": [
"cdk deploy"
],
"commands": [
"npm install",
"cdk bootstrap",
"cdk deploy"
]
},
"testing": {
"text": [
"Publish events via HTTP endpoint"
]
},
"cleanup": {
"text": [
"cdk destroy"
],
"commands": [
"cdk destroy"
]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
],
"services": {
"from": [
{
"service": "appsync"
}
],
"to": [
{
"service": "lambda"
}
]
}
}
79 changes: 79 additions & 0 deletions appsync-events-lambda-cdk/lib/appsync-events-lambda-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as cdk from 'aws-cdk-lib';
import * as appsync from 'aws-cdk-lib/aws-appsync';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

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

// Lambda handler for event processing
const eventFn = new lambda.Function(this, 'EventHandlerFn', {
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
timeout: cdk.Duration.seconds(10),
logRetention: logs.RetentionDays.ONE_WEEK,
});

// IAM role for AppSync to invoke Lambda
const appsyncRole = new iam.Role(this, 'AppSyncLambdaRole', {
assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'),
});
eventFn.grantInvoke(appsyncRole);

// IAM role for AppSync to push logs to CloudWatch
const logsRole = new iam.Role(this, 'ApiLogsRole', {
assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs'),
],
});

// AppSync Events API with unique name derived from stack name
const api = new appsync.CfnApi(this, 'EventsApi', {
name: `${cdk.Aws.STACK_NAME}-EventsApi`,
eventConfig: {
authProviders: [{ authType: 'API_KEY' }],
connectionAuthModes: [{ authType: 'API_KEY' }],
defaultPublishAuthModes: [{ authType: 'API_KEY' }],
defaultSubscribeAuthModes: [{ authType: 'API_KEY' }],
logConfig: {
logLevel: 'INFO',
cloudWatchLogsRoleArn: logsRole.roleArn,
},
},
});

// API key with 365-day expiry
const apiKey = new appsync.CfnApiKey(this, 'EventsApiKey', {
apiId: api.attrApiId,
expires: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60,
});

// Channel namespace wired to Lambda event handler
new appsync.CfnChannelNamespace(this, 'NotificationsChannel', {
apiId: api.attrApiId,
name: 'notifications',
publishAuthModes: [{ authType: 'API_KEY' }],
subscribeAuthModes: [{ authType: 'API_KEY' }],
codeHandlers: "import { util } from '@aws-appsync/utils';\nexport function onPublish(ctx) {\n return { events: ctx.events };\n}",
});

// Second channel namespace
new appsync.CfnChannelNamespace(this, 'AlertsChannel', {
apiId: api.attrApiId,
name: 'alerts',
publishAuthModes: [{ authType: 'API_KEY' }],
subscribeAuthModes: [{ authType: 'API_KEY' }],
});

new cdk.CfnOutput(this, 'HttpEndpoint', { value: cdk.Fn.getAtt(api.logicalId, 'Dns.Http').toString() });
new cdk.CfnOutput(this, 'RealtimeEndpoint', { value: cdk.Fn.getAtt(api.logicalId, 'Dns.Realtime').toString() });
new cdk.CfnOutput(this, 'ApiId', { value: api.attrApiId });
new cdk.CfnOutput(this, 'ApiKeyValue', { value: apiKey.attrApiKey });
new cdk.CfnOutput(this, 'FunctionName', { value: eventFn.functionName });
}
}
14 changes: 14 additions & 0 deletions appsync-events-lambda-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "appsync-events-lambda-cdk",
"version": "1.0.0",
"bin": { "app": "bin/app.js" },
"scripts": { "build": "tsc", "cdk": "cdk" },
"dependencies": {
"aws-cdk-lib": "^2.180.0",
"constructs": "^10.0.0"
},
"devDependencies": {
"typescript": "~5.4.0",
"@types/node": "^20.0.0"
}
}
26 changes: 26 additions & 0 deletions appsync-events-lambda-cdk/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
exports.handler = async (event) => {
const { events } = event;

if (!events || !Array.isArray(events)) {
return { events: [{ payload: { error: 'No events received' } }] };
}

const processed = events.map(e => {
let payload;
try {
payload = JSON.parse(e.payload);
} catch (err) {
return { payload: JSON.stringify({ error: 'Invalid JSON payload' }) };
}
return {
payload: JSON.stringify({
...payload,
processedAt: new Date().toISOString(),
enriched: true,
messageId: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
}),
};
});

return { events: processed };
};
13 changes: 13 additions & 0 deletions appsync-events-lambda-cdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["es2020"],
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"outDir": "build",
"declaration": true
},
"exclude": ["node_modules", "cdk.out"]
}
64 changes: 64 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Amazon EventBridge Data Plane Logging with AWS CloudTrail

This pattern enables CloudTrail data plane logging for Amazon EventBridge and triggers a Lambda function when PutEvents API calls are detected, providing security and operational visibility into event bus activity.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/eventbridge-cloudtrail-dataplane-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: Mention that CloudTrail data events are billed separately and link pricing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a callout box in the README noting CloudTrail data events are billed separately, with a link to the pricing page.


> **Note:** CloudTrail data events are billed separately from management events. See [AWS CloudTrail Pricing](https://aws.amazon.com/cloudtrail/pricing/) for current data event rates.

## Requirements

* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed
* [Node.js 20+](https://nodejs.org/en/download/) installed

## Deployment Instructions

1. Clone and navigate to the pattern:
```
cd serverless-patterns/eventbridge-cloudtrail-dataplane-cdk
npm install
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add cdk bootstrap for first time users

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a cdk bootstrap step in the deployment instructions.

2. Bootstrap CDK (first-time CDK users only):
```
cdk bootstrap
```
3. Deploy:
```
cdk deploy
```

## How it works

- A CloudTrail trail is created with data event logging enabled for EventBridge event buses (`AWS::Events::EventBus`)
- EventBridge data plane API calls (PutEvents) are now logged to CloudTrail (new May 2026 feature)
- An EventBridge rule captures these CloudTrail events matching `aws.events` source with `PutEvents` event name
- A Lambda function processes the events, logging the caller identity, source IP, event bus, and entry count
- This enables security teams to audit who is putting events to which bus

## Testing

```bash
# Put a test event to the default event bus
aws events put-events --entries '[{"Source":"test.app","DetailType":"TestEvent","Detail":"{\"key\":\"value\"}"}]'

# Check Lambda logs (allow ~5 minutes for CloudTrail delivery)
aws logs tail /aws/lambda/$(aws cloudformation describe-stacks \
--stack-name EventbridgeCloudtrailDataplaneStack \
--query 'Stacks[0].Outputs[?OutputKey==`ProcessorFunctionName`].OutputValue' --output text) \
--follow
```

## Cleanup

```
cdk destroy
```

---

Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
7 changes: 7 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { EventbridgeCloudtrailDataplaneStack } from '../lib/eventbridge-cloudtrail-dataplane-stack';

const app = new cdk.App();
new EventbridgeCloudtrailDataplaneStack(app, 'EventbridgeCloudtrailDataplaneStack');
3 changes: 3 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node --prefer-ts-exts bin/app.ts"
}
Loading