Skip to content

Commit c772def

Browse files
authored
Merge pull request #2967 from ginjups/ginjups-df-webhook-nodejs-sam
adding nodejs webhook df pattern
2 parents 820cad1 + 6dcb8f1 commit c772def

10 files changed

Lines changed: 891 additions & 0 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Webhook Receiver with AWS Lambda durable functions - NodeJS
2+
3+
This serverless pattern demonstrates a serverless webhook receiver using AWS Lambda durable functions with NodeJS. The pattern receives webhook events via API Gateway, processes them durably with automatic checkpointing, and provides status query capabilities.
4+
5+
Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/testing/patterns/lambda-durable-webhook-sam-nodejs
6+
7+
To Learn more about Lambda durable functions:
8+
- [AWS Lambda durable functions Documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
9+
- [Lambda durable functions Best Practices](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-best-practices.html)
10+
11+
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.
12+
13+
## Requirements
14+
15+
* [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.
16+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
17+
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
18+
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed
19+
20+
## Architecture
21+
22+
![Architecture Diagram](architecture.png)
23+
24+
## How It Works
25+
26+
This pattern demonstrates a serverless webhook receiver using AWS Lambda durable functions. The pattern receives webhook events via API Gateway, processes them durably with automatic checkpointing, and provides status query capabilities. The durable function processes webhooks in 3 checkpointed steps:
27+
28+
1. **Validate** - Verify webhook payload and structure
29+
2. **Process** - Execute business logic on webhook data
30+
3. **Finalize** - Complete processing and update final status
31+
32+
This pattern acheives the following key features:
33+
34+
- **Automatic Checkpointing** - Each processing step is checkpointed automatically
35+
- **Failure Recovery** - Resumes from last checkpoint on failure
36+
- **Asynchronous Processing** - Immediate 202 response, processing in background
37+
- **State Persistence** - Execution state stored in DynamoDB with TTL
38+
- **Status Query API** - Real-time status tracking via REST API
39+
40+
**Note:** Each step writes status updates to DynamoDB before its main work. These writes are idempotent, so retries are safe. During replay, the DynamoDB status reflects the last successfully written state—not the current replay position. Status queries should treat intermediate states as "in progress."
41+
42+
**Important:** Please check the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html) for regions currently supported by AWS Lambda durable functions.
43+
44+
## Deployment
45+
46+
1. **Build the application**:
47+
```bash
48+
sam build
49+
```
50+
51+
2. **Deploy to AWS**:
52+
Plese enter required `WebhookSecret`
53+
54+
```bash
55+
sam deploy --guided
56+
```
57+
58+
Note the outputs after deployment:
59+
- `WebhookApiUrl`: Use this for sending webhook POST requests
60+
- `StatusQueryApiUrl`: Use this for querying execution status
61+
62+
## Testing
63+
To test the set-up, utilize the below curl command by replacing the WebhookApiUrl copied from the above step:
64+
65+
```bash
66+
# Send a test webhook
67+
curl -X POST <WebhookApiUrl> \
68+
-H "Content-Type: application/json" \
69+
-d '{
70+
"type": "order",
71+
"orderId": "123456",
72+
"data": {"amount": 100}
73+
}'
74+
```
75+
76+
Once the Webhook is submitted, to query the status of webhook, use the following curl command by replacing the StatusQueryApiUrl:
77+
78+
```bash
79+
# Get execution status (use executionToken from webhook response)
80+
curl <StatusQueryApiUrl>
81+
```
82+
83+
**Success indicators:**
84+
- Webhook returns 202 with `executionToken`
85+
- Status query shows progression: `STARTED``VALIDATING``PROCESSING``COMPLETED`
86+
- Execution state persists in DynamoDB with TTL
87+
88+
To simulate a validation failure, send an invalid payload (empty or missing required fields):
89+
90+
```bash
91+
# Send an invalid webhook (empty payload triggers validation failure)
92+
curl -X POST <WebhookApiUrl> \
93+
-H "Content-Type: application/json" \
94+
-d '{}'
95+
```
96+
97+
Query the status to see the failure:
98+
99+
```bash
100+
curl <StatusQueryApiUrl>
101+
```
102+
103+
**Failure indicators:**
104+
- Status query shows `FAILED` status
105+
- Error message indicates validation failure reason
106+
- Execution state persists in DynamoDB for debugging
107+
108+
## Cleanup
109+
110+
```bash
111+
sam delete
112+
```
113+
----
114+
Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
115+
116+
SPDX-License-Identifier: MIT-0
50.3 KB
Loading
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"title": "Webhook Receiver with AWS Lambda durable functions - NodeJS",
3+
"description": "This serverless pattern demonstrates building a webhook receiver using AWS Lambda durable functions with automatic checkpointing and fault tolerance, implemented in Node.js",
4+
"language": "Node.js",
5+
"level": "200",
6+
"framework": "AWS SAM",
7+
"services": ["apigateway","lambda", "dynamoDB"],
8+
"introBox": {
9+
"headline": "How it works",
10+
"text": [
11+
"This pattern demonstrates a serverless webhook receiver using AWS Lambda durable functions. When a webhook POST request arrives via API Gateway, it triggers a durable function that processes the webhook in 3 checkpointed steps: Validate → Process → Finalize. Each step is automatically checkpointed, allowing the workflow to resume from the last successful step if interrupted. The pattern provides immediate 202 response while processing continues in the background, stores execution state in DynamoDB with TTL, and offers real-time status tracking via a REST API."
12+
]
13+
},
14+
"testing": {
15+
"headline": "Testing",
16+
"text": [
17+
"See the GitHub repo for detailed testing instructions."
18+
]
19+
},
20+
"cleanup": {
21+
"headline": "Cleanup",
22+
"text": [
23+
"Delete the stack: <code>sam delete</code>."
24+
]
25+
},
26+
"deploy": {
27+
"text": [
28+
"sam build",
29+
"sam deploy --guided"
30+
]
31+
},
32+
"gitHub": {
33+
"template": {
34+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-webhook-sam-nodejs",
35+
"templateURL":"serverless-patterns/lambda-durable-webhook-sam-nodejs",
36+
"templateFile": "template.yaml",
37+
"projectFolder": "lambda-durable-webhook-sam-nodejs"
38+
}
39+
},
40+
"resources": {
41+
"headline": "Additional resources",
42+
"bullets": [
43+
{
44+
"text": "AWS Lambda durable functions Documentation",
45+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
46+
},
47+
{
48+
"text": "Event Source Mappings with Lambda durable functions",
49+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking-esm.html"
50+
},
51+
{
52+
"text": "Lambda durable functions Best Practices",
53+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions-best-practices.html"
54+
},
55+
{
56+
"text": "Node.js AWS SDK Documentation",
57+
"link": "https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/"
58+
}
59+
]
60+
},
61+
"authors": [
62+
{
63+
"name": "Sahithi Ginjupalli",
64+
"image": "https://drive.google.com/file/d/1YcKYuGz3LfzSxiwb2lWJfpyi49SbvOSr/view?usp=sharing",
65+
"bio": "Cloud Engineer at AWS with a passion for diving deep into cloud and AI services to build innovative serverless applications.",
66+
"linkedin": "ginjupalli-sahithi-37460a18b",
67+
"twitter": ""
68+
}
69+
]
70+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
2+
const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');
3+
4+
// Initialize AWS clients
5+
const dynamodbClient = new DynamoDBClient({});
6+
const dynamodb = DynamoDBDocumentClient.from(dynamodbClient);
7+
8+
/**
9+
* Status query function for webhook processing
10+
* Allows real-time status tracking via REST API
11+
*/
12+
exports.handler = async (event, context) => {
13+
const executionToken = event.pathParameters?.executionToken;
14+
const eventsTableName = process.env.EVENTS_TABLE_NAME;
15+
16+
console.log(`Querying status for execution token: ${executionToken}`);
17+
18+
if (!executionToken) {
19+
return {
20+
statusCode: 400,
21+
headers: {
22+
'Content-Type': 'application/json',
23+
'Access-Control-Allow-Origin': '*'
24+
},
25+
body: JSON.stringify({
26+
error: 'Missing executionToken parameter'
27+
})
28+
};
29+
}
30+
31+
try {
32+
// Query execution state from DynamoDB
33+
const result = await dynamodb.send(new GetCommand({
34+
TableName: eventsTableName,
35+
Key: { executionToken }
36+
}));
37+
38+
if (!result.Item) {
39+
return {
40+
statusCode: 404,
41+
headers: {
42+
'Content-Type': 'application/json',
43+
'Access-Control-Allow-Origin': '*'
44+
},
45+
body: JSON.stringify({
46+
error: 'Execution token not found',
47+
executionToken: executionToken
48+
})
49+
};
50+
}
51+
52+
// Format response based on current status
53+
const execution = result.Item;
54+
const response = {
55+
executionToken: executionToken,
56+
status: execution.status,
57+
timestamp: execution.timestamp,
58+
currentStep: execution.currentStep || 'unknown'
59+
};
60+
61+
// Add additional fields based on status
62+
if (execution.status === 'COMPLETED') {
63+
response.result = execution.result;
64+
response.completedAt = execution.completedAt;
65+
}
66+
67+
if (execution.status === 'FAILED') {
68+
response.error = execution.error;
69+
}
70+
71+
if (execution.payload) {
72+
response.originalPayload = execution.payload;
73+
}
74+
75+
return {
76+
statusCode: 200,
77+
headers: {
78+
'Content-Type': 'application/json',
79+
'Access-Control-Allow-Origin': '*'
80+
},
81+
body: JSON.stringify(response)
82+
};
83+
84+
} catch (error) {
85+
console.error(`Error querying status for ${executionToken}:`, error.message);
86+
87+
return {
88+
statusCode: 500,
89+
headers: {
90+
'Content-Type': 'application/json',
91+
'Access-Control-Allow-Origin': '*'
92+
},
93+
body: JSON.stringify({
94+
error: 'Failed to query execution status',
95+
executionToken: executionToken,
96+
message: error.message
97+
})
98+
};
99+
}
100+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "status-query-function",
3+
"version": "1.0.0",
4+
"description": "Status query function for webhook processing",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"dependencies": {
10+
"@aws-sdk/client-dynamodb": "^3.700.0",
11+
"@aws-sdk/lib-dynamodb": "^3.700.0"
12+
},
13+
"author": "",
14+
"license": "MIT"
15+
}

0 commit comments

Comments
 (0)