diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b1b87f65..32ab7a72 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -32,4 +32,4 @@ Contribution Steps:
4. Open up a pr
## Converting Lambda Fns to CDK Language
-This would be a big help and is a great way to get started contributing. When a pattern launches typically I build it in TypeScript and then port it over to Python. In order to reduce bugs at launch I typically reuse the JS Lambdda Fn in the Python version then slowly refactor over time to full Python. If you spot one of these JS functions in a Python pattern and want to convert it to Python it is small enough that you could just do it and open a Pull Request
+This would be a big help and is a great way to get started contributing. When a pattern launches typically I build it in TypeScript and then port it over to Python. In order to reduce bugs at launch I typically reuse the JS Lambda Fn in the Python version then slowly refactor over time to full Python. If you spot one of these JS functions in a Python pattern and want to convert it to Python it is small enough that you could just do it and open a Pull Request
diff --git a/the-scalable-webhook/README.md b/the-scalable-webhook/README.md
index add75042..23a37a67 100644
--- a/the-scalable-webhook/README.md
+++ b/the-scalable-webhook/README.md
@@ -78,3 +78,5 @@ we used an id we generated in the message as the key
* [TypeScript](typescript/)
* [Python](python/)
+ * [CSharp](csharp/)
+ * [Java](java/)
diff --git a/the-scalable-webhook/csharp/.gitignore b/the-scalable-webhook/csharp/.gitignore
new file mode 100644
index 00000000..bb50c8be
--- /dev/null
+++ b/the-scalable-webhook/csharp/.gitignore
@@ -0,0 +1,340 @@
+# CDK asset staging directory
+.cdk.staging
+cdk.out
+
+# Created by https://www.gitignore.io/api/csharp
+
+### Csharp ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+
+# End of https://www.gitignore.io/api/csharp
\ No newline at end of file
diff --git a/the-scalable-webhook/csharp/README.md b/the-scalable-webhook/csharp/README.md
new file mode 100644
index 00000000..a918399c
--- /dev/null
+++ b/the-scalable-webhook/csharp/README.md
@@ -0,0 +1,116 @@
+# The Scalable Webhook
+
+This is an example CDK stack to deploy The Scalable Webhook stack described by Jeremy Daly here - https://www.jeremydaly.com/serverless-microservice-patterns-for-aws/#scalablewebhook
+
+An advanced version of this pattern was talked about by [Heitor Lessa](https://twitter.com/heitor_lessa) at re:Invent 2019 as Call me, “Maybe” (Webhook)
+
+* [Youtube Recording](https://www.youtube.com/watch?v=9IYpGTS7Jy0)
+* [Static Slides](https://d1.awsstatic.com/events/reinvent/2019/REPEAT_3_Serverless_architectural_patterns_and_best_practices_ARC307-R3.pdf)
+
+## Desconstructing The Scalable Webhook
+If you want a walkthrough of the theory, the code and finally a demo of the deployed implementation check out:
+
+[![Alt text](https://img.youtube.com/vi/kRI7QJfGBI8/0.jpg)](https://www.youtube.com/watch?v=kRI7QJfGBI8)
+
+## High Level Description
+You would use this pattern when you have a non serverless resource like an RDS DB in direct contact with a serverless resource like a lambda. You need to make
+sure that your serverless resource doesn't scale up to an amount that it DOS attacks your non serverless resource.
+
+This is done by putting a queue between them and having a lambda with a throttled concurrency policy pull items off the queue and communicate with your
+serverless resource at a rate it can handle.
+
+![Architecture](https://raw.githubusercontent.com/cdk-patterns/serverless/master/the-scalable-webhook/img/architecture.png)
+
+NOTE: For this pattern in the cdk deployable construct I have swapped RDS for DynamoDB.
Why? Because it is significantly cheaper/faster for developers to deploy and maintain, I also don't think we lose the essence of the pattern with this swap given we still do the pub/sub deduplication via SQS/Lambda and throttle the subscription lambda. RDS also introduces extra complexity in that it needs to be deployed in a VPC. I am slightly worried developers would get distracted by the extra RDS logic when the main point is the pattern. A real life implementation of this pattern could use RDS MySQL or it could be a call to an on-prem mainframe, the main purpose of the pattern is the throttling to not overload the scale-limited resource.
+
+## Pattern Background
+
+When people move to the cloud (especially serverless) they tend to think that this means their applications are now infinitely scalable:
+
+![aws scalability](img/aws_scalability.png)
+
+For the right reasons this just isn't true. If any one person's resources were infinitely scalable then any one person could consume the whole of AWS no matter how scalable the platform.
+
+![aws reality](img/aws_reality.png)
+
+- [View API Gateway limits](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html)
+- [View Lambda limits](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html)
+- [View DynamoDB Limits](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html)
+
+
+If we weren't using DynamoDB, we would need to know the max connections limit configured for our instance size:
+
+![MySQL reality](img/mysql.png)
+![Postgres reality](img/postgres.png)
+
+We need to slow down the amount of direct requests to our DB somehow, that is where the scalable webhook comes in:
+
+![scalable webhook](img/scalable_webhook.png)
+
+We can use SQS to hold all requests in a queue as soon as they come in. Again, SQS will have limits:
+
+![sqs limits](img/sqs.png)
+
+120,000 in flight messages with an unlimited backlog I think will be effective enough as a buffer.
+
+[View SQS quotas](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-quotas.html)
+
+Now we have our messages in a queue but we need to subscribe to the queue and insert the records into the DB. To do this we create a throttled lambda where we set the max number of concurrent executions to whatever scale we are happy with. This should be less than the max connections on our DB and should take into account any other Lambdas running in this account.
+
+![throttle](img/throttle.png)
+
+One final improvement that we could make if implementing this in a production system is to delete the Lambda between the API Gateway and SQS. You can do a direct integration which will reduce costs and latency:
+
+![More scalable Webhook](img/more_scalable_webhook.png)
+
+If you want an AWS managed service to try and help with this scalability problem you can check out [AWS RDS Proxy](https://aws.amazon.com/rds/proxy/) which is in preview
+
+![rds proxy](img/rds_proxy.png)
+
+## How to test pattern
+
+When you deploy this you will have an API Gateway where any url is routed through to the publish lambda. If you modify the url from / to say /hello this url will be sent as a message via sqs to a lambda
+which will insert "hello from /hello" into dynamodb as a message. You can track the progress of your message at every stage through cloudwatch as logs are printed, you can view the contents of
+dynamo in the console and the contents of sqs in the console. You should also notice that SQS can include duplicate messages but in those instances you don't get two identical records in DynamoDB as
+we used an id we generated in the message as the key
+
+# Useful commands
+
+* `dotnet build src` compile this app
+* `cdk deploy` deploy this stack to your default AWS account/region
+* `cdk diff` compare deployed stack with current state
+* `cdk synth` emits the synthesized CloudFormation template
+
+## Deploy with AWS Cloud9
+
+* Create an **Ubuntu** AWS Cloud9 EC2 development environment
+* Add the Microsoft repository
+ ```
+ wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
+ ```
+ ```
+ sudo dpkg -i packages-microsoft-prod.deb
+ ```
+* Install the .NET Core SDK
+ ```
+ sudo apt-get update; \
+ sudo apt-get install -y apt-transport-https && \
+ sudo apt-get update && \
+ sudo apt-get install -y dotnet-sdk-3.1
+ ```
+* Clone the CDK Patterns repo
+ ```
+ git clone https://github.com/cdk-patterns/serverless.git
+ ```
+* Change directory
+ ```
+ cd serverless/the-scalable-webhook/csharp
+ ```
+* Build the project to see if .NET Core has been setup correctly (optional)
+ ```
+ dotnet build src
+ ```
+* Deploy the stack
+ ```
+ cdk deploy
+ ```
diff --git a/the-scalable-webhook/csharp/cdk.json b/the-scalable-webhook/csharp/cdk.json
new file mode 100644
index 00000000..52347e55
--- /dev/null
+++ b/the-scalable-webhook/csharp/cdk.json
@@ -0,0 +1,11 @@
+{
+ "app": "dotnet run -p src/TheScalableWebhook/TheScalableWebhook.csproj",
+ "context": {
+ "@aws-cdk/core:enableStackNameDuplicates": "true",
+ "aws-cdk:enableDiffNoFail": "true",
+ "@aws-cdk/core:stackRelativeExports": "true",
+ "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
+ "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
+ "@aws-cdk/aws-kms:defaultKeyPolicies": true
+ }
+}
diff --git a/the-scalable-webhook/csharp/img/architecture.png b/the-scalable-webhook/csharp/img/architecture.png
new file mode 100644
index 00000000..5fc4ef32
Binary files /dev/null and b/the-scalable-webhook/csharp/img/architecture.png differ
diff --git a/the-scalable-webhook/csharp/img/aws_reality.png b/the-scalable-webhook/csharp/img/aws_reality.png
new file mode 100644
index 00000000..c44688af
Binary files /dev/null and b/the-scalable-webhook/csharp/img/aws_reality.png differ
diff --git a/the-scalable-webhook/csharp/img/aws_scalability.png b/the-scalable-webhook/csharp/img/aws_scalability.png
new file mode 100644
index 00000000..cb24e68b
Binary files /dev/null and b/the-scalable-webhook/csharp/img/aws_scalability.png differ
diff --git a/the-scalable-webhook/csharp/img/more_scalable_webhook.png b/the-scalable-webhook/csharp/img/more_scalable_webhook.png
new file mode 100644
index 00000000..4fe0752a
Binary files /dev/null and b/the-scalable-webhook/csharp/img/more_scalable_webhook.png differ
diff --git a/the-scalable-webhook/csharp/img/mysql.png b/the-scalable-webhook/csharp/img/mysql.png
new file mode 100644
index 00000000..34641a1d
Binary files /dev/null and b/the-scalable-webhook/csharp/img/mysql.png differ
diff --git a/the-scalable-webhook/csharp/img/postgres.png b/the-scalable-webhook/csharp/img/postgres.png
new file mode 100644
index 00000000..bf929aea
Binary files /dev/null and b/the-scalable-webhook/csharp/img/postgres.png differ
diff --git a/the-scalable-webhook/csharp/img/rds_proxy.png b/the-scalable-webhook/csharp/img/rds_proxy.png
new file mode 100644
index 00000000..4d43a01c
Binary files /dev/null and b/the-scalable-webhook/csharp/img/rds_proxy.png differ
diff --git a/the-scalable-webhook/csharp/img/scalable_webhook.png b/the-scalable-webhook/csharp/img/scalable_webhook.png
new file mode 100644
index 00000000..f5f88592
Binary files /dev/null and b/the-scalable-webhook/csharp/img/scalable_webhook.png differ
diff --git a/the-scalable-webhook/csharp/img/sqs.png b/the-scalable-webhook/csharp/img/sqs.png
new file mode 100644
index 00000000..5d8a25fc
Binary files /dev/null and b/the-scalable-webhook/csharp/img/sqs.png differ
diff --git a/the-scalable-webhook/csharp/img/throttle.png b/the-scalable-webhook/csharp/img/throttle.png
new file mode 100644
index 00000000..183f34ed
Binary files /dev/null and b/the-scalable-webhook/csharp/img/throttle.png differ
diff --git a/the-scalable-webhook/csharp/lambda_fns/publish/lambda.js b/the-scalable-webhook/csharp/lambda_fns/publish/lambda.js
new file mode 100644
index 00000000..a3debc0c
--- /dev/null
+++ b/the-scalable-webhook/csharp/lambda_fns/publish/lambda.js
@@ -0,0 +1,42 @@
+"use strict";
+var AWS = require('aws-sdk');
+exports.handler = async function (event) {
+ console.log("request:", JSON.stringify(event, undefined, 2));
+ // Create an SQS service object
+ var sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
+ var params = {
+ DelaySeconds: 10,
+ MessageAttributes: {
+ MessageDeduplicationId: {
+ DataType: "String",
+ StringValue: event.path + new Date().getTime()
+ }
+ },
+ MessageBody: "hello from " + event.path,
+ QueueUrl: process.env.queueURL,
+ };
+ let response;
+ await sqs.sendMessage(params, function (err, data) {
+ if (err) {
+ console.log("Error", err);
+ response = sendRes(500, err);
+ }
+ else {
+ console.log("Success", data.MessageId);
+ response = sendRes(200, 'You have added a message to the queue! Message ID is ' + data.MessageId);
+ }
+ }).promise();
+ // return response back to upstream caller
+ return response;
+};
+let sendRes = (status, body) => {
+ var response = {
+ statusCode: status,
+ headers: {
+ "Content-Type": "text/html"
+ },
+ body: body
+ };
+ return response;
+};
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGFtYmRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibGFtYmRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxJQUFJLEdBQUcsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFFN0IsT0FBTyxDQUFDLE9BQU8sR0FBRyxLQUFLLFdBQVUsS0FBUztJQUN4QyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUU3RCwrQkFBK0I7SUFDL0IsSUFBSSxHQUFHLEdBQUcsSUFBSSxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUMsVUFBVSxFQUFFLFlBQVksRUFBQyxDQUFDLENBQUM7SUFFbEQsSUFBSSxNQUFNLEdBQUc7UUFDWCxZQUFZLEVBQUUsRUFBRTtRQUNoQixpQkFBaUIsRUFBRTtZQUNqQixzQkFBc0IsRUFBRTtnQkFDdEIsUUFBUSxFQUFFLFFBQVE7Z0JBQ2xCLFdBQVcsRUFBRSxLQUFLLENBQUMsSUFBSSxHQUFHLElBQUksSUFBSSxFQUFFLENBQUMsT0FBTyxFQUFFO2FBQy9DO1NBQ0Y7UUFDRCxXQUFXLEVBQUUsYUFBYSxHQUFDLEtBQUssQ0FBQyxJQUFJO1FBQ3JDLFFBQVEsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVE7S0FDL0IsQ0FBQztJQUVGLElBQUksUUFBUSxDQUFDO0lBRWIsTUFBTSxHQUFHLENBQUMsV0FBVyxDQUFDLE1BQU0sRUFBRSxVQUFTLEdBQU8sRUFBRSxJQUFRO1FBQ3RELElBQUksR0FBRyxFQUFFO1lBQ1AsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFDMUIsUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUE7U0FDN0I7YUFBTTtZQUNMLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUN2QyxRQUFRLEdBQUcsT0FBTyxDQUFDLEdBQUcsRUFBRSx1REFBdUQsR0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUE7U0FDaEc7SUFDSCxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUViLDBDQUEwQztJQUMxQyxPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDLENBQUM7QUFFRixJQUFJLE9BQU8sR0FBRyxDQUFDLE1BQWEsRUFBRSxJQUFXLEVBQUUsRUFBRTtJQUMzQyxJQUFJLFFBQVEsR0FBRztRQUNiLFVBQVUsRUFBRSxNQUFNO1FBQ2xCLE9BQU8sRUFBRTtZQUNQLGNBQWMsRUFBRSxXQUFXO1NBQzVCO1FBQ0QsSUFBSSxFQUFFLElBQUk7S0FDWCxDQUFDO0lBQ0YsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsidmFyIEFXUyA9IHJlcXVpcmUoJ2F3cy1zZGsnKTtcclxuXHJcbmV4cG9ydHMuaGFuZGxlciA9IGFzeW5jIGZ1bmN0aW9uKGV2ZW50OmFueSkge1xyXG4gIGNvbnNvbGUubG9nKFwicmVxdWVzdDpcIiwgSlNPTi5zdHJpbmdpZnkoZXZlbnQsIHVuZGVmaW5lZCwgMikpO1xyXG5cclxuICAvLyBDcmVhdGUgYW4gU1FTIHNlcnZpY2Ugb2JqZWN0XHJcbiAgdmFyIHNxcyA9IG5ldyBBV1MuU1FTKHthcGlWZXJzaW9uOiAnMjAxMi0xMS0wNSd9KTtcclxuXHJcbiAgdmFyIHBhcmFtcyA9IHtcclxuICAgIERlbGF5U2Vjb25kczogMTAsXHJcbiAgICBNZXNzYWdlQXR0cmlidXRlczoge1xyXG4gICAgICBNZXNzYWdlRGVkdXBsaWNhdGlvbklkOiB7XHJcbiAgICAgICAgRGF0YVR5cGU6IFwiU3RyaW5nXCIsXHJcbiAgICAgICAgU3RyaW5nVmFsdWU6IGV2ZW50LnBhdGggKyBuZXcgRGF0ZSgpLmdldFRpbWUoKVxyXG4gICAgICB9XHJcbiAgICB9LFxyXG4gICAgTWVzc2FnZUJvZHk6IFwiaGVsbG8gZnJvbSBcIitldmVudC5wYXRoLFxyXG4gICAgUXVldWVVcmw6IHByb2Nlc3MuZW52LnF1ZXVlVVJMLFxyXG4gIH07XHJcblxyXG4gIGxldCByZXNwb25zZTtcclxuXHJcbiAgYXdhaXQgc3FzLnNlbmRNZXNzYWdlKHBhcmFtcywgZnVuY3Rpb24oZXJyOmFueSwgZGF0YTphbnkpIHtcclxuICAgIGlmIChlcnIpIHtcclxuICAgICAgY29uc29sZS5sb2coXCJFcnJvclwiLCBlcnIpO1xyXG4gICAgICByZXNwb25zZSA9IHNlbmRSZXMoNTAwLCBlcnIpXHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICBjb25zb2xlLmxvZyhcIlN1Y2Nlc3NcIiwgZGF0YS5NZXNzYWdlSWQpO1xyXG4gICAgICByZXNwb25zZSA9IHNlbmRSZXMoMjAwLCAnWW91IGhhdmUgYWRkZWQgYSBtZXNzYWdlIHRvIHRoZSBxdWV1ZSEgTWVzc2FnZSBJRCBpcyAnK2RhdGEuTWVzc2FnZUlkKVxyXG4gICAgfVxyXG4gIH0pLnByb21pc2UoKTtcclxuXHJcbiAgLy8gcmV0dXJuIHJlc3BvbnNlIGJhY2sgdG8gdXBzdHJlYW0gY2FsbGVyXHJcbiAgcmV0dXJuIHJlc3BvbnNlO1xyXG59O1xyXG5cclxubGV0IHNlbmRSZXMgPSAoc3RhdHVzOm51bWJlciwgYm9keTpzdHJpbmcpID0+IHtcclxuICB2YXIgcmVzcG9uc2UgPSB7XHJcbiAgICBzdGF0dXNDb2RlOiBzdGF0dXMsXHJcbiAgICBoZWFkZXJzOiB7XHJcbiAgICAgIFwiQ29udGVudC1UeXBlXCI6IFwidGV4dC9odG1sXCJcclxuICAgIH0sXHJcbiAgICBib2R5OiBib2R5XHJcbiAgfTtcclxuICByZXR1cm4gcmVzcG9uc2U7XHJcbn07Il19
\ No newline at end of file
diff --git a/the-scalable-webhook/csharp/lambda_fns/subscribe/lambda.js b/the-scalable-webhook/csharp/lambda_fns/subscribe/lambda.js
new file mode 100644
index 00000000..e66eb4f5
--- /dev/null
+++ b/the-scalable-webhook/csharp/lambda_fns/subscribe/lambda.js
@@ -0,0 +1,30 @@
+"use strict";
+const { DynamoDB } = require('aws-sdk');
+exports.handler = async function (event) {
+ console.log("request:", JSON.stringify(event, undefined, 2));
+ let records = event.Records;
+ // create AWS SDK clients
+ const dynamo = new DynamoDB();
+ for (let index in records) {
+ let payload = records[index].body;
+ let id = records[index].messageAttributes.MessageDeduplicationId.stringValue;
+ console.log('received message ' + payload);
+ var params = {
+ TableName: process.env.tableName,
+ Item: {
+ 'id': { S: id },
+ 'message': { S: payload }
+ }
+ };
+ // Call DynamoDB to add the item to the table
+ await dynamo.putItem(params, function (err, data) {
+ if (err) {
+ console.log("Error", err);
+ }
+ else {
+ console.log("Success", data);
+ }
+ }).promise();
+ }
+};
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGFtYmRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibGFtYmRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxNQUFNLEVBQUUsUUFBUSxFQUFFLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRXhDLE9BQU8sQ0FBQyxPQUFPLEdBQUcsS0FBSyxXQUFVLEtBQVM7SUFDeEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFN0QsSUFBSSxPQUFPLEdBQVUsS0FBSyxDQUFDLE9BQU8sQ0FBQztJQUNuQyx5QkFBeUI7SUFDekIsTUFBTSxNQUFNLEdBQUcsSUFBSSxRQUFRLEVBQUUsQ0FBQztJQUU5QixLQUFJLElBQUksS0FBSyxJQUFJLE9BQU8sRUFBRTtRQUN4QixJQUFJLE9BQU8sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQ2xDLElBQUksRUFBRSxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxzQkFBc0IsQ0FBQyxXQUFXLENBQUE7UUFDNUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsR0FBRyxPQUFPLENBQUMsQ0FBQztRQUczQyxJQUFJLE1BQU0sR0FBRztZQUNYLFNBQVMsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVM7WUFDaEMsSUFBSSxFQUFFO2dCQUNKLElBQUksRUFBRyxFQUFDLENBQUMsRUFBRSxFQUFFLEVBQUM7Z0JBQ2QsU0FBUyxFQUFHLEVBQUMsQ0FBQyxFQUFFLE9BQU8sRUFBQzthQUN6QjtTQUNGLENBQUM7UUFFRiw2Q0FBNkM7UUFDN0MsTUFBTSxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxVQUFTLEdBQU8sRUFBRSxJQUFRO1lBQ3JELElBQUksR0FBRyxFQUFFO2dCQUNQLE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLEdBQUcsQ0FBQyxDQUFDO2FBQzNCO2lCQUFNO2dCQUNMLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDO2FBQzlCO1FBQ0gsQ0FBQyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7S0FDZDtBQUNILENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHsgRHluYW1vREIgfSA9IHJlcXVpcmUoJ2F3cy1zZGsnKTtcclxuXHJcbmV4cG9ydHMuaGFuZGxlciA9IGFzeW5jIGZ1bmN0aW9uKGV2ZW50OmFueSkge1xyXG4gIGNvbnNvbGUubG9nKFwicmVxdWVzdDpcIiwgSlNPTi5zdHJpbmdpZnkoZXZlbnQsIHVuZGVmaW5lZCwgMikpO1xyXG5cclxuICBsZXQgcmVjb3JkczogYW55W10gPSBldmVudC5SZWNvcmRzO1xyXG4gIC8vIGNyZWF0ZSBBV1MgU0RLIGNsaWVudHNcclxuICBjb25zdCBkeW5hbW8gPSBuZXcgRHluYW1vREIoKTtcclxuICBcclxuICBmb3IobGV0IGluZGV4IGluIHJlY29yZHMpIHtcclxuICAgIGxldCBwYXlsb2FkID0gcmVjb3Jkc1tpbmRleF0uYm9keTtcclxuICAgIGxldCBpZCA9IHJlY29yZHNbaW5kZXhdLm1lc3NhZ2VBdHRyaWJ1dGVzLk1lc3NhZ2VEZWR1cGxpY2F0aW9uSWQuc3RyaW5nVmFsdWVcclxuICAgIGNvbnNvbGUubG9nKCdyZWNlaXZlZCBtZXNzYWdlICcgKyBwYXlsb2FkKTtcclxuICAgIFxyXG4gICAgXHJcbiAgICB2YXIgcGFyYW1zID0ge1xyXG4gICAgICBUYWJsZU5hbWU6IHByb2Nlc3MuZW52LnRhYmxlTmFtZSxcclxuICAgICAgSXRlbToge1xyXG4gICAgICAgICdpZCcgOiB7UzogaWR9LFxyXG4gICAgICAgICdtZXNzYWdlJyA6IHtTOiBwYXlsb2FkfVxyXG4gICAgICB9XHJcbiAgICB9O1xyXG4gICAgXHJcbiAgICAvLyBDYWxsIER5bmFtb0RCIHRvIGFkZCB0aGUgaXRlbSB0byB0aGUgdGFibGVcclxuICAgIGF3YWl0IGR5bmFtby5wdXRJdGVtKHBhcmFtcywgZnVuY3Rpb24oZXJyOmFueSwgZGF0YTphbnkpIHtcclxuICAgICAgaWYgKGVycikge1xyXG4gICAgICAgIGNvbnNvbGUubG9nKFwiRXJyb3JcIiwgZXJyKTtcclxuICAgICAgfSBlbHNlIHtcclxuICAgICAgICBjb25zb2xlLmxvZyhcIlN1Y2Nlc3NcIiwgZGF0YSk7XHJcbiAgICAgIH1cclxuICAgIH0pLnByb21pc2UoKTtcclxuICB9XHJcbn07Il19
\ No newline at end of file
diff --git a/the-scalable-webhook/csharp/src/TheScalableWebhook.sln b/the-scalable-webhook/csharp/src/TheScalableWebhook.sln
new file mode 100644
index 00000000..d153411c
--- /dev/null
+++ b/the-scalable-webhook/csharp/src/TheScalableWebhook.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheScalableWebhook", "TheScalableWebhook\TheScalableWebhook.csproj", "{F6BD738B-81E0-4F32-8A62-CCEC3D757A31}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Debug|x64.Build.0 = Debug|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Debug|x86.Build.0 = Debug|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Release|x64.ActiveCfg = Release|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Release|x64.Build.0 = Release|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Release|x86.ActiveCfg = Release|Any CPU
+ {F6BD738B-81E0-4F32-8A62-CCEC3D757A31}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/the-scalable-webhook/csharp/src/TheScalableWebhook/GlobalSuppressions.cs b/the-scalable-webhook/csharp/src/TheScalableWebhook/GlobalSuppressions.cs
new file mode 100644
index 00000000..26233fcb
--- /dev/null
+++ b/the-scalable-webhook/csharp/src/TheScalableWebhook/GlobalSuppressions.cs
@@ -0,0 +1 @@
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Potential Code Quality Issues", "RECS0026:Possible unassigned object created by 'new'", Justification = "Constructs add themselves to the scope in which they are created")]
diff --git a/the-scalable-webhook/csharp/src/TheScalableWebhook/Program.cs b/the-scalable-webhook/csharp/src/TheScalableWebhook/Program.cs
new file mode 100644
index 00000000..7f9885e2
--- /dev/null
+++ b/the-scalable-webhook/csharp/src/TheScalableWebhook/Program.cs
@@ -0,0 +1,17 @@
+using Amazon.CDK;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TheScalableWebhook
+{
+ sealed class Program
+ {
+ public static void Main(string[] args)
+ {
+ var app = new App();
+ new TheScalableWebhookStack(app, "TheScalableWebhookStack");
+ app.Synth();
+ }
+ }
+}
diff --git a/the-scalable-webhook/csharp/src/TheScalableWebhook/TheScalableWebhook.csproj b/the-scalable-webhook/csharp/src/TheScalableWebhook/TheScalableWebhook.csproj
new file mode 100644
index 00000000..460a68dd
--- /dev/null
+++ b/the-scalable-webhook/csharp/src/TheScalableWebhook/TheScalableWebhook.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+ Major
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/the-scalable-webhook/csharp/src/TheScalableWebhook/TheScalableWebhookStack.cs b/the-scalable-webhook/csharp/src/TheScalableWebhook/TheScalableWebhookStack.cs
new file mode 100644
index 00000000..810041cb
--- /dev/null
+++ b/the-scalable-webhook/csharp/src/TheScalableWebhook/TheScalableWebhookStack.cs
@@ -0,0 +1,94 @@
+using Amazon.CDK;
+using Lambda = Amazon.CDK.AWS.Lambda;
+using LambdaEvents = Amazon.CDK.AWS.Lambda.EventSources;
+using APIG = Amazon.CDK.AWS.APIGateway;
+using SQS = Amazon.CDK.AWS.SQS;
+using DynamoDB = Amazon.CDK.AWS.DynamoDB;
+using System.Collections.Generic;
+
+namespace TheScalableWebhook
+{
+ public class TheScalableWebhookStack : Stack
+ {
+ // declaring all constructors
+ readonly private DynamoDB.Table _dynamoDbTable;
+ readonly private SQS.Queue _queueRds;
+ readonly private Lambda.Function _functionPublish;
+ readonly private Lambda.Function _functionSubscribe;
+
+ internal TheScalableWebhookStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
+ {
+ /*
+ * DynamoDB Table
+ * This is standing in for what is RDS on the diagram due to simpler/cheaper setup
+ */
+ _dynamoDbTable = new DynamoDB.Table(this, "Messages", new DynamoDB.TableProps
+ {
+ PartitionKey = new DynamoDB.Attribute {
+ Name = "id", Type = DynamoDB.AttributeType.STRING
+ }
+ });
+
+ /*
+ * Queue Setup
+ */
+ _queueRds = new SQS.Queue(this, "RDSPublishQueue", new SQS.QueueProps
+ {
+ VisibilityTimeout = Duration.Seconds(300)
+ });
+
+ /*
+ * Lambdas
+ * Both publisher and subscriber from pattern
+ */
+
+ /*
+ * defines an AWS Lambda resource to publish to our sqs_queue
+ */
+ _functionPublish = new Lambda.Function(this, "SQSPublishLambdaHandler", new Lambda.FunctionProps
+ {
+ Runtime = Lambda.Runtime.NODEJS_12_X, // execution environment
+ Handler = "lambda.handler", // file is "lambda", function is "handler"
+ Code = Lambda.Code.FromAsset("lambda_fns/publish"), // code loaded from the "lambda_fns/publish" directory
+ Environment = new Dictionary
+ {
+ { "queueURL", _queueRds.QueueUrl }
+ }
+ });
+
+ _queueRds.GrantSendMessages(_functionPublish);
+
+ /*
+ * defines an AWS Lambda resource to pull from our sqs_queue
+ */
+ _functionSubscribe = new Lambda.Function(this, "SQSSubscribeLambdaHandler", new Lambda.FunctionProps
+ {
+ Runtime = Lambda.Runtime.NODEJS_12_X, // execution environment
+ Handler = "lambda.handler", // file is "lambda", function is "handler"
+ Code = Lambda.Code.FromAsset("lambda_fns/subscribe"), // code loaded from the "lambda_fns/subscribe" directory
+ Environment = new Dictionary
+ {
+ { "queueURL", _queueRds.QueueUrl },
+ { "tableName", _dynamoDbTable.TableName }
+ },
+ ReservedConcurrentExecutions = 2 // throttle lambda to 2 concurrent invocations
+ });
+
+ _queueRds.GrantConsumeMessages(_functionSubscribe);
+ _functionSubscribe.AddEventSource(new LambdaEvents.SqsEventSource(_queueRds));
+ _dynamoDbTable.GrantReadWriteData(_functionSubscribe);
+
+ /**
+ * API Gateway Proxy
+ * Used to expose the webhook through a URL
+ *
+ * defines an API Gateway REST API resource backed by our "sqs_publish_lambda" function.
+ */
+ new APIG.LambdaRestApi(this, "Endpoint", new APIG.LambdaRestApiProps
+ {
+ Handler = _functionPublish
+ });
+
+ }
+ }
+}
diff --git a/the-scalable-webhook/java/.gitignore b/the-scalable-webhook/java/.gitignore
new file mode 100644
index 00000000..1db21f16
--- /dev/null
+++ b/the-scalable-webhook/java/.gitignore
@@ -0,0 +1,13 @@
+.classpath.txt
+target
+.classpath
+.project
+.idea
+.settings
+.vscode
+*.iml
+
+# CDK asset staging directory
+.cdk.staging
+cdk.out
+
diff --git a/the-scalable-webhook/java/README.md b/the-scalable-webhook/java/README.md
new file mode 100644
index 00000000..70f18242
--- /dev/null
+++ b/the-scalable-webhook/java/README.md
@@ -0,0 +1,84 @@
+# The Scalable Webhook
+
+This is an example CDK stack to deploy The Scalable Webhook stack described by Jeremy Daly here - https://www.jeremydaly.com/serverless-microservice-patterns-for-aws/#scalablewebhook
+
+An advanced version of this pattern was talked about by [Heitor Lessa](https://twitter.com/heitor_lessa) at re:Invent 2019 as Call me, “Maybe” (Webhook)
+
+* [Youtube Recording](https://www.youtube.com/watch?v=9IYpGTS7Jy0)
+* [Static Slides](https://d1.awsstatic.com/events/reinvent/2019/REPEAT_3_Serverless_architectural_patterns_and_best_practices_ARC307-R3.pdf)
+
+## Desconstructing The Scalable Webhook
+If you want a walkthrough of the theory, the code and finally a demo of the deployed implementation check out:
+
+[![Alt text](https://img.youtube.com/vi/kRI7QJfGBI8/0.jpg)](https://www.youtube.com/watch?v=kRI7QJfGBI8)
+
+## High Level Description
+You would use this pattern when you have a non serverless resource like an RDS DB in direct contact with a serverless resource like a lambda. You need to make
+sure that your serverless resource doesn't scale up to an amount that it DOS attacks your non serverless resource.
+
+This is done by putting a queue between them and having a lambda with a throttled concurrency policy pull items off the queue and communicate with your
+serverless resource at a rate it can handle.
+
+![Architecture](https://raw.githubusercontent.com/cdk-patterns/serverless/master/the-scalable-webhook/img/architecture.png)
+
+NOTE: For this pattern in the cdk deployable construct I have swapped RDS for DynamoDB.