diff --git a/apigw-sqs-lambda-ddb/README.md b/apigw-sqs-lambda-ddb/README.md
new file mode 100644
index 000000000..072f4b4fe
--- /dev/null
+++ b/apigw-sqs-lambda-ddb/README.md
@@ -0,0 +1,123 @@
+# Amazon API Gateway to Amazon SQS to AWS Lambda to Amazon DynamoDB
+
+This pattern explains how to deploy a SAM application with Amazon API Gateway, Amazon SQS, AWS Lambda, AWS Secrets Manager and Amazon DynamoDB.
+
+This pattern is useful to accept and respond to requests quickly but offloading the processing as asynchronous process e.g., webhook endpoints. Once the request is placed in SQS, API gateway responds back to the caller immediately without waiting for those messages to be processed.
+
+When an HTTP POST request is made to the Amazon API Gateway endpoint, Gateway authorizes the request by checking Basic auth credentials against values in AWS Secrets manager. When credentials are valid, request payload is sent to Amazon Simple Queue Service. AWS Lambda function consumes event from the Queue and inserts the event/payload into the Amazon DynamoDB table. Amazon Simple Queue Service is also configured with a Dead Letter Queue where events are sent when retries to process those messages are repeatedly failed.
+
+Key Benefits:
+- Operations: AWS services used in this pattern can scale easily and quickly based on incoming traffic.
+- Security: The APIs created with Amazon API Gateway expose HTTPS endpoints only. All user data stored in Amazon DynamoDB and Amazon SQS is fully encrypted at rest. Secrets are securely stored in AWS Secrets Manager.
+- Reliability: A dead-letter queue ensures no messages are lost due to issues. Helps debugging failed messages and once underlying issue is resolved, messages can be redrived back to source queues for processing.
+- Performance: AWS Lambda supports batching with Amazon SQS. Lambda reads messages in batches and invokes the function once for each batch.
+- Cost: Pay only for what you use. No minimum fee.
+
+
+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. You are responsible for any AWS costs incurred. No warranty is implied in this example.
+
+## Requirements
+
+* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
+* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
+* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed
+
+## Deployment Instructions
+
+1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
+ ```
+ git clone https://github.com/aws-samples/serverless-patterns
+ ```
+1. Change directory to the pattern directory:
+ ```
+ cd apigw-sqs-lambda-ddb
+ ```
+1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
+ ```
+ sam deploy --guided
+ ```
+1. During the prompts:
+ * Enter a stack name
+ * Enter the desired AWS Region
+ * Allow SAM CLI to create IAM roles with the required permissions.
+
+ Once you have run `sam deploy -guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults.
+
+1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing.
+
+## How it works
+
+
+
+- This pattern deploys an Amazon API Gateway HTTP API with route /submit configured with basic authentication.
+- On receiving a request, API Gateway will invoke a Lambda authorizer which validates the request and returns a policy informing API Gateway to accept or deny the request.
+- When request is accepted, API Gateway sends the message payload to a queue in SQS. SQS uses another queue as Dead Letter Queue to send the messages in case of continued failures to process the messages from downstream lambda.
+- Messages from SQS is posted to a lambda function to process them.
+- Lambda function receives the messages from SQS and saves them into a DynamoDB table.
+
+## Testing
+
+Once the application is deployed, follow the below steps to test this pattern:
+
+### Retrieve Information
+- Retrieve the HttpApiEndpoint value from CloudFormation Outputs
+- Retrieve the username and password from Secrets Manager in AWS Console. Base64 encoding of username and password joined by a single colon : will be used as {Credential} in Authorization header.
+
+### Construct Request
+- Replace the placeholders in the Curl request given below with retrieved values.
+- Region: AWS Region used for deployment e.g., us-east-1
+
+Example POST Request (Curl):
+```
+curl --location 'https://{HttpApiEndpoint}.execute-api.{Region}.amazonaws.com/submit' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic {Credentials}' \
+--data '{
+ "eventId": "f5337ee6-e13e-4bcb-8082-872898b5d1a8",
+ "message": "Sample message for testing"
+}'
+```
+
+### Verify
+- Execute the fully formed Curl request and verify that you received a successful response with an XML message. Response is basically an XML representation of SQS SendMessage response.
+Example:
+```
+
+
+
+ 9c22e4eb-09f6-4374-95b8-f6030f33da33
+ 93355f3c10001c53dcae387ea9d5b71e
+
+
+ 84728dba-affe-50d7-8e62-d08a5bb32a38
+
+
+```
+
+- View the contents of DynamoDB Table "EventTable" from AWS Console and verify that JSON data used in your payload is saved as a record.
+
+
+### Steps To Verify Dead-Letter Queue
+
+- Messages should be moved to Dead-Letter Queue when there's repeated failures in processing the message by Lambda function.
+- Lambda which consumes the message from source queue (BufferingQueue) expects the message to be in JSON format. When the message is not in JSON format, it will fail to process it. To verify that messages are moved to Dead-Letter Queue, use a request which doesn't use a JSON payload. Example:
+```
+curl --location 'https://{HttpApiEndpoint}.execute-api.{Region}.amazonaws.com/submit' \
+--header 'Content-Type: application/json' \
+--header 'Authorization: Basic {Credentials}' \
+--data 'Test Message'
+```
+- After few minutes, login to AWS console and verify that the Dead-Letter queue (DeadLetterQueue) has available messages greater than 0. You can also try "Poll for messages" in this queue to verify the message body.
+
+## Cleanup
+
+1. Delete the stack
+ ```bash
+ sam delete
+ ```
+
+----
+Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+SPDX-License-Identifier: MIT-0
\ No newline at end of file
diff --git a/apigw-sqs-lambda-ddb/api.yaml b/apigw-sqs-lambda-ddb/api.yaml
new file mode 100644
index 000000000..291a6d382
--- /dev/null
+++ b/apigw-sqs-lambda-ddb/api.yaml
@@ -0,0 +1,30 @@
+openapi: "3.0.1"
+info:
+ title: "Http Api"
+ version: "2023-06-25 17:32:29UTC"
+paths:
+ /submit:
+ post:
+ responses:
+ default:
+ description: "Response for POST /"
+ x-amazon-apigateway-integration:
+ type: "aws_proxy"
+ integrationSubtype: "SQS-SendMessage"
+ credentials:
+ Fn::GetAtt: [ApiGatewayRole, Arn]
+ connectionType: "INTERNET"
+ payloadFormatVersion: "1.0"
+ requestParameters:
+ MessageBody: "$request.body"
+ QueueUrl:
+ Ref: BufferingQueue
+
+x-amazon-apigateway-cors:
+ allowMethods:
+ - "*"
+ maxAge: 0
+ allowCredentials: false
+ allowOrigins:
+ - "*"
+x-amazon-apigateway-importexport-version: "1.0"
diff --git a/apigw-sqs-lambda-ddb/docs/apigw-sqs-lambda-ddb.drawio b/apigw-sqs-lambda-ddb/docs/apigw-sqs-lambda-ddb.drawio
new file mode 100644
index 000000000..02d7506cb
--- /dev/null
+++ b/apigw-sqs-lambda-ddb/docs/apigw-sqs-lambda-ddb.drawio
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apigw-sqs-lambda-ddb/docs/apigw-sqs-lambda-ddb.drawio.png b/apigw-sqs-lambda-ddb/docs/apigw-sqs-lambda-ddb.drawio.png
new file mode 100644
index 000000000..2a9e8f88d
Binary files /dev/null and b/apigw-sqs-lambda-ddb/docs/apigw-sqs-lambda-ddb.drawio.png differ
diff --git a/apigw-sqs-lambda-ddb/example-pattern.json b/apigw-sqs-lambda-ddb/example-pattern.json
new file mode 100644
index 000000000..9ca9defd3
--- /dev/null
+++ b/apigw-sqs-lambda-ddb/example-pattern.json
@@ -0,0 +1,63 @@
+{
+ "title": "Amazon API Gateway to Amazon SQS to AWS Lambda to Amazon DynamoDB",
+ "description": "This pattern explains how to deploy a SAM application with Amazon API Gateway, Amazon SQS, AWS Lambda, and Amazon DynamoDB. When an HTTP POST request is made to the Amazon API Gateway endpoint, Gateway authorizes the request by checking Basic auth credentials and on valid credentials, request payload is sent to Amazon Simple Queue Service. AWS Lambda function consumes event from the Queue and inserts the event/payload into the Amazon DynamoDB table. Amazon Simple Queue Service is also configured with a Dead Letter Queue where events are sent when retries to process those messages are repeatedly failed.",
+ "language": "Python",
+ "level": "300",
+ "framework": "SAM",
+ "introBox": {
+ "headline": "How it works",
+ "text": [
+ "This pattern deploys an Amazon API Gateway HTTP API with route /submit configured with basic authentication.",
+ "On receiving a request, API Gateway will invoke a Lambda authorizer which validates the request and returns a policy informing API Gateway to accept or deny the request.",
+ "When request is accepted, API Gateway sends the message payload to a queue in SQS. SQS uses another queue as Dead Letter Queue to send the messages in case of continued failures to process the messages from downstream lambda.",
+ "Messages from SQS is posted to a lambda function to process them.",
+ "Lambda function receives the messages from SQS and saves them into a DynamoDB table."
+ ]
+ },
+ "gitHub": {
+ "template": {
+ "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-sqs-lambda-ddb",
+ "templateURL": "serverless-patterns/apigw-sqs-lambda-ddb",
+ "projectFolder": "apigw-sqs-lambda-ddb",
+ "templateFile": "apigw-sqs-lambda-ddb/template.yaml"
+ }
+ },
+ "resources": {
+ "bullets": [
+ {
+ "text": "Lambda Authorizers",
+ "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"
+ },
+ {
+ "text": "Amazon SQS dead-letter queues",
+ "link": "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html"
+ },
+ {
+ "text": "Working with HTTP APIs",
+ "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html"
+ }
+ ]
+ },
+ "deploy": {
+ "text": [
+ "sam deploy"
+ ]
+ },
+ "testing": {
+ "text": [
+ "See the GitHub repo for detailed testing instructions."
+ ]
+ },
+ "cleanup": {
+ "text": [
+ "Delete the stack: sam delete
."
+ ]
+ },
+ "authors": [
+ {
+ "name": "Ravi Kiran Ganji",
+ "bio": "I am a Senior Cloud Application Architect at AWS Professional Services, and Serverless Enthusiast.",
+ "linkedin": "ravi-kiran-ganji"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apigw-sqs-lambda-ddb/lambda/authorizer/index.py b/apigw-sqs-lambda-ddb/lambda/authorizer/index.py
new file mode 100644
index 000000000..100353e6b
--- /dev/null
+++ b/apigw-sqs-lambda-ddb/lambda/authorizer/index.py
@@ -0,0 +1,39 @@
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.utilities.typing import LambdaContext
+import boto3
+import json
+import base64
+
+logger = Logger()
+tracer = Tracer()
+client = boto3.client("secretsmanager")
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ username = get_secret("endpoint/username", "UserName")
+ password = get_secret("endpoint/password", "Password")
+ expected_auth_header = "Basic " + base64_encode(f"{username}:{password}")
+
+ is_authorized = False
+ if (
+ "authorization" in event["headers"]
+ and event["headers"]["authorization"] == expected_auth_header
+ ):
+ is_authorized = True
+
+ return {"isAuthorized": is_authorized}
+
+
+# Method to read secret from secrets manager
+def get_secret(secret_name: str, secret_key: str) -> str:
+ response = client.get_secret_value(SecretId=secret_name)
+ value = json.loads(response["SecretString"])
+ return value[secret_key]
+
+
+# Method to base64 encode string
+def base64_encode(string: str) -> str:
+ return base64.b64encode(string.encode("utf-8")).decode("utf-8")
diff --git a/apigw-sqs-lambda-ddb/lambda/sqs-handler/index.py b/apigw-sqs-lambda-ddb/lambda/sqs-handler/index.py
new file mode 100644
index 000000000..c691e8416
--- /dev/null
+++ b/apigw-sqs-lambda-ddb/lambda/sqs-handler/index.py
@@ -0,0 +1,20 @@
+from aws_lambda_powertools import Logger, Tracer
+from aws_lambda_powertools.logging import correlation_paths
+from aws_lambda_powertools.utilities.typing import LambdaContext
+import json
+import boto3
+
+logger = Logger()
+tracer = Tracer()
+table = boto3.resource("dynamodb").Table("EventTable")
+
+
+@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
+@tracer.capture_lambda_handler
+def lambda_handler(event: dict, context: LambdaContext) -> dict:
+ # process records from sqs event
+ for record in event["Records"]:
+ payload = json.loads(record["body"])
+ table.put_item(Item=payload)
+
+ return {"statusCode": 200}
diff --git a/apigw-sqs-lambda-ddb/template.yaml b/apigw-sqs-lambda-ddb/template.yaml
new file mode 100644
index 000000000..7c353edc7
--- /dev/null
+++ b/apigw-sqs-lambda-ddb/template.yaml
@@ -0,0 +1,274 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Transform: AWS::Serverless-2016-10-31
+Description: A endpoint hosted by an API Gateway which invokes SQS, consumed by lambda function and persisted in a dynamoDB table.
+
+Resources:
+ ##########################################################################
+ # HTTP API - Entrypoint #
+ ##########################################################################
+ HttpApi:
+ Type: AWS::Serverless::HttpApi
+ Properties:
+ AccessLogSettings:
+ DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn
+ Format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength" }'
+ DefinitionBody:
+ "Fn::Transform":
+ Name: "AWS::Include"
+ Parameters:
+ Location: "api.yaml"
+ Auth:
+ DefaultAuthorizer: AuthorizerFunction
+ Authorizers:
+ AuthorizerFunction:
+ FunctionArn: !GetAtt GatewayAuthorizerFunction.Arn
+ AuthorizerPayloadFormatVersion: 2.0
+ EnableFunctionDefaultPermissions: true
+ EnableSimpleResponses: true
+
+ ##########################################################################
+ # SQS Queues #
+ ##########################################################################
+ # SQS queue for request buffering
+ BufferingQueue:
+ Type: AWS::SQS::Queue
+ Properties:
+ QueueName: BufferingQueue
+ SqsManagedSseEnabled: true
+ RedrivePolicy:
+ deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
+ maxReceiveCount: 5
+
+ # SQS DLQ
+ DeadLetterQueue:
+ Type: AWS::SQS::Queue
+ Properties:
+ QueueName: DeadLetterQueue
+ SqsManagedSseEnabled: true
+
+ ##########################################################################
+ # Lambda Function - Gateway Handler #
+ ##########################################################################
+ SqsHandlerFunction:
+ Type: AWS::Serverless::Function
+ DependsOn: SqsHandlerLambdaExecutionRole
+ Properties:
+ FunctionName: SQSHandlerFunction
+ Description: Lambda to be invoked by the SQS Queue
+ CodeUri: lambda/sqs-handler/
+ Handler: index.lambda_handler
+ Runtime: python3.10
+ Timeout: 3
+ MemorySize: 128
+ Role: !GetAtt SqsHandlerLambdaExecutionRole.Arn
+ Layers:
+ [
+ !Sub "arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:32",
+ ]
+ Events:
+ SQSEvent:
+ Type: SQS
+ Properties:
+ Queue: !GetAtt BufferingQueue.Arn
+ BatchSize: 10
+
+ ##########################################################################
+ # Lambda Function - Gateway Authorizer #
+ ##########################################################################
+ GatewayAuthorizerFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ FunctionName: GatewayAuthorizerFunction
+ Description: Lambda to be invoked by API Gateway for authorization
+ CodeUri: lambda/authorizer/
+ Handler: index.lambda_handler
+ Runtime: python3.10
+ Timeout: 3
+ MemorySize: 128
+ Role: !GetAtt AuthorizerLambdaExecutionRole.Arn
+ Layers:
+ [
+ !Sub "arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV2:32",
+ ]
+
+ ##########################################################################
+ # DynamoDB Table #
+ ##########################################################################
+ EventTable:
+ Type: AWS::DynamoDB::Table
+ Properties:
+ AttributeDefinitions:
+ - AttributeName: eventId
+ AttributeType: S
+ BillingMode: PAY_PER_REQUEST
+ KeySchema:
+ - AttributeName: eventId
+ KeyType: HASH
+ TableName: EventTable
+ SSESpecification:
+ SSEEnabled: true
+
+ ##########################################################################
+ # Roles #
+ ##########################################################################
+ ApiGatewayRole:
+ Type: "AWS::IAM::Role"
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - apigateway.amazonaws.com
+ Action:
+ - "sts:AssumeRole"
+ Policies:
+ - PolicyName: CustomPolicy
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - "sqs:SendMessage"
+ - "sqs:GetQueueUrl"
+ - "sqs:SendMessageBatch"
+ Resource: !GetAtt BufferingQueue.Arn
+ - Effect: Allow
+ Action:
+ - "logs:CreateLogGroup"
+ - "logs:CreateLogStream"
+ - "logs:DescribeLogGroups"
+ - "logs:DescribeLogStreams"
+ - "logs:PutLogEvents"
+ - "logs:GetLogEvents"
+ - "logs:FilterLogEvents"
+ Resource: !GetAtt ApiGatewayAccessLogs.Arn
+
+ SqsHandlerLambdaExecutionRole:
+ Type: "AWS::IAM::Role"
+ DependsOn: EventTable
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - lambda.amazonaws.com
+ Action:
+ - "sts:AssumeRole"
+ Policies:
+ - PolicyName: CustomPolicy
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - "dynamodb:List*"
+ - "dynamodb:DescribeReservedCapacity*"
+ - "dynamodb:DescribeLimits"
+ - "dynamodb:DescribeTimeToLive"
+ - "dynamodb:Get*"
+ - "dynamodb:PutItem"
+ Resource: !GetAtt EventTable.Arn
+ - Effect: Allow
+ Action:
+ - "logs:CreateLogGroup"
+ - "logs:CreateLogStream"
+ - "logs:PutLogEvents"
+ Resource: "*"
+ - Effect: Allow
+ Action:
+ - "sqs:ReceiveMessage"
+ - "sqs:DeleteMessage"
+ - "sqs:GetQueueAttributes"
+ Resource: !GetAtt BufferingQueue.Arn
+ - Effect: Allow
+ Action:
+ - "sqs:SendMessage"
+ Resource: !GetAtt DeadLetterQueue.Arn
+
+ AuthorizerLambdaExecutionRole:
+ Type: "AWS::IAM::Role"
+ Properties:
+ AssumeRolePolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - lambda.amazonaws.com
+ Action:
+ - "sts:AssumeRole"
+ Policies:
+ - PolicyName: CustomPolicy
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - "logs:CreateLogGroup"
+ - "logs:CreateLogStream"
+ - "logs:PutLogEvents"
+ Resource: "*"
+ - Effect: Allow
+ Action:
+ - "secretsmanager:GetSecretValue"
+ Resource: [!Ref BasicAuthUsername, !Ref BasicAuthPassword]
+
+ ##########################################################################
+ # Cloudwatch Logs #
+ ##########################################################################
+
+ ApiGatewayAccessLogs:
+ Type: AWS::Logs::LogGroup
+ Properties:
+ LogGroupName: Http-ApiGw-Access-Logs
+ RetentionInDays: 1
+
+ ##########################################################################
+ # Secrets - Generated for Endpoint Auth #
+ ##########################################################################
+
+ BasicAuthUsername:
+ Type: AWS::SecretsManager::Secret
+ Properties:
+ Description: Username to be used in basic auth
+ Name: endpoint/username
+ GenerateSecretString:
+ ExcludeCharacters: ""
+ ExcludeLowercase: false
+ ExcludeNumbers: false
+ ExcludePunctuation: false
+ ExcludeUppercase: false
+ GenerateStringKey: UserName
+ IncludeSpace: false
+ PasswordLength: 32
+ RequireEachIncludedType: true
+ SecretStringTemplate: !Sub '{"UserName": "REPLACED"}'
+
+ BasicAuthPassword:
+ Type: AWS::SecretsManager::Secret
+ Properties:
+ Description: Password to be used in basic auth
+ Name: endpoint/password
+ GenerateSecretString:
+ ExcludeCharacters: ""
+ ExcludeLowercase: false
+ ExcludeNumbers: false
+ ExcludePunctuation: false
+ ExcludeUppercase: false
+ GenerateStringKey: Password
+ IncludeSpace: false
+ PasswordLength: 32
+ RequireEachIncludedType: true
+ SecretStringTemplate: !Sub '{"Password": "REPLACED"}'
+
+##########################################################################
+# Outputs #
+##########################################################################
+Outputs:
+ HttpApiEndpoint:
+ Description: "Http API endpoint"
+ Value: !Sub "https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com"