Skip to content

Commit a64a263

Browse files
committed
Address PR review comments from biswanathmukherjee
- Fix service name casing to 'Lambda durable functions' per official docs - Change framework to 'AWS SAM' in example-pattern.json - Add API Gateway REST API resource link - Remove technical exception details from status_query error response - Replace print() with structured logger.info() in webhook_processor - Add StepConfig(max_attempts=1) to validate step to prevent retrying permanent failures - Fix test imports: validate_signature from webhook_validator
1 parent a27426f commit a64a263

6 files changed

Lines changed: 344 additions & 336 deletions

File tree

lambda-durable-webhook-sam-python/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Webhook Receiver with AWS Lambda Durable Functions (Python)
1+
# Webhook Receiver with Lambda durable functions (Python)
22

3-
This pattern demonstrates a serverless webhook receiver using AWS Lambda Durable Functions with Python. The pattern receives webhook events via API Gateway, processes them durably with automatic checkpointing, and provides status query capabilities.
3+
This pattern demonstrates a serverless webhook receiver using Lambda durable functions with Python. The pattern receives webhook events via API Gateway, processes them durably with automatic checkpointing, and provides status query capabilities.
44

55
**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.
66

@@ -173,7 +173,7 @@ Stored event: 1234567890123, status: completed
173173

174174
### Durable Execution
175175

176-
The webhook processor uses AWS Lambda Durable Functions to:
176+
The webhook processor uses Lambda durable functions to:
177177
1. **Checkpoint automatically** after each step
178178
2. **Persist state** to DynamoDB
179179
3. **Resume from last checkpoint** on failure
@@ -352,7 +352,7 @@ See [SECURITY.md](SECURITY.md) for detailed security recommendations.
352352

353353
## Learn More
354354

355-
- [AWS Lambda Durable Functions Documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
355+
- [Lambda durable functions documentation](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
356356
- [AWS SAM Documentation](https://docs.aws.amazon.com/serverless-application-model/)
357357
- [Serverless Land Patterns](https://serverlessland.com/patterns)
358358

Lines changed: 68 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,68 @@
1-
{
2-
"title": "Webhook Receiver with AWS Lambda Durable Functions",
3-
"description": "Receive and process webhooks durably with automatic checkpointing using Lambda Durable Functions and Python",
4-
"language": "Python",
5-
"level": "200",
6-
"framework": "SAM",
7-
"introBox": {
8-
"headline": "How it works",
9-
"text": [
10-
"This pattern demonstrates a serverless webhook receiver using AWS Lambda Durable Functions with Python.",
11-
"The pattern receives webhook events via API Gateway, processes them through multiple checkpointed steps, and provides status query capabilities.",
12-
"Each processing step is automatically checkpointed, allowing the workflow to resume from the last successful step if interrupted.",
13-
"Webhook events and processing state are persisted in DynamoDB with automatic TTL cleanup after 7 days."
14-
]
15-
},
16-
"gitHub": {
17-
"template": {
18-
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-webhook-sam-python",
19-
"templateURL": "serverless-patterns/lambda-durable-webhook-sam-python",
20-
"projectFolder": "lambda-durable-webhook-sam-python",
21-
"templateFile": "template.yaml"
22-
}
23-
},
24-
"resources": {
25-
"bullets": [
26-
{
27-
"text": "AWS Lambda Durable Functions Documentation",
28-
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
29-
},
30-
{
31-
"text": "Durable Execution SDK for Python",
32-
"link": "https://github.com/aws/aws-durable-execution-sdk-python"
33-
},
34-
{
35-
"text": "AWS SAM Documentation",
36-
"link": "https://docs.aws.amazon.com/serverless-application-model/"
37-
},
38-
{
39-
"text": "API Gateway HTTP API",
40-
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html"
41-
}
42-
]
43-
},
44-
"deploy": {
45-
"text": [
46-
"sam build",
47-
"sam deploy --guided"
48-
]
49-
},
50-
"testing": {
51-
"text": [
52-
"See the GitHub repo for detailed testing instructions."
53-
]
54-
},
55-
"cleanup": {
56-
"text": [
57-
"sam delete --stack-name lambda-durable-webhook"
58-
]
59-
},
60-
"authors": [
61-
{
62-
"name": "AWS Serverless Patterns",
63-
"image": "https://serverlessland.com/assets/images/resources/contributors/aws.png",
64-
"bio": "AWS Serverless Patterns Collection",
65-
"linkedin": "aws"
66-
}
67-
]
68-
}
1+
{
2+
"title": "Webhook Receiver with Lambda durable functions",
3+
"description": "Receive and process webhooks durably with automatic checkpointing using Lambda durable functions and Python",
4+
"language": "Python",
5+
"level": "200",
6+
"framework": "AWS SAM",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"This pattern demonstrates a serverless webhook receiver using Lambda durable functions with Python.",
11+
"The pattern receives webhook events via API Gateway, processes them through multiple checkpointed steps, and provides status query capabilities.",
12+
"Each processing step is automatically checkpointed, allowing the workflow to resume from the last successful step if interrupted.",
13+
"Webhook events and processing state are persisted in DynamoDB with automatic TTL cleanup after 7 days."
14+
]
15+
},
16+
"gitHub": {
17+
"template": {
18+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-webhook-sam-python",
19+
"templateURL": "serverless-patterns/lambda-durable-webhook-sam-python",
20+
"projectFolder": "lambda-durable-webhook-sam-python",
21+
"templateFile": "template.yaml"
22+
}
23+
},
24+
"resources": {
25+
"bullets": [
26+
{
27+
"text": "Lambda durable functions documentation",
28+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
29+
},
30+
{
31+
"text": "Durable execution SDK for Python",
32+
"link": "https://github.com/aws/aws-durable-execution-sdk-python"
33+
},
34+
{
35+
"text": "AWS SAM documentation",
36+
"link": "https://docs.aws.amazon.com/serverless-application-model/"
37+
},
38+
{
39+
"text": "API Gateway REST API",
40+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html"
41+
}
42+
]
43+
},
44+
"deploy": {
45+
"text": [
46+
"sam build",
47+
"sam deploy --guided"
48+
]
49+
},
50+
"testing": {
51+
"text": [
52+
"See the GitHub repo for detailed testing instructions."
53+
]
54+
},
55+
"cleanup": {
56+
"text": [
57+
"sam delete --stack-name lambda-durable-webhook"
58+
]
59+
},
60+
"authors": [
61+
{
62+
"name": "AWS Serverless Patterns",
63+
"image": "https://serverlessland.com/assets/images/resources/contributors/aws.png",
64+
"bio": "AWS Serverless Patterns Collection",
65+
"linkedin": "aws"
66+
}
67+
]
68+
}
Lines changed: 111 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,111 @@
1-
"""
2-
Status Query Function
3-
Retrieves webhook processing status from DynamoDB
4-
"""
5-
import json
6-
import os
7-
from typing import Dict, Any
8-
import boto3
9-
from boto3.dynamodb.conditions import Key
10-
11-
dynamodb = boto3.resource('dynamodb')
12-
table = dynamodb.Table(os.environ['EVENTS_TABLE_NAME'])
13-
14-
15-
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
16-
"""
17-
Query webhook processing status
18-
19-
Args:
20-
event: API Gateway event with executionToken in path parameters
21-
context: Lambda context
22-
23-
Returns:
24-
HTTP response with status information
25-
"""
26-
print(f"Status query event: {json.dumps(event)}")
27-
28-
# Extract execution token from path parameters
29-
execution_token = event.get('pathParameters', {}).get('executionToken')
30-
31-
if not execution_token:
32-
return {
33-
'statusCode': 400,
34-
'headers': {
35-
'Content-Type': 'application/json',
36-
'Access-Control-Allow-Origin': '*'
37-
},
38-
'body': json.dumps({
39-
'error': 'Missing executionToken in path'
40-
})
41-
}
42-
43-
print(f"Querying status for execution token: {execution_token}")
44-
45-
try:
46-
# Query DynamoDB for the execution token
47-
response = table.get_item(
48-
Key={'executionToken': execution_token}
49-
)
50-
51-
if 'Item' not in response:
52-
print(f"Execution token not found: {execution_token}")
53-
return {
54-
'statusCode': 404,
55-
'headers': {
56-
'Content-Type': 'application/json',
57-
'Access-Control-Allow-Origin': '*'
58-
},
59-
'body': json.dumps({
60-
'error': 'Execution token not found',
61-
'executionToken': execution_token
62-
})
63-
}
64-
65-
item = response['Item']
66-
67-
# Build response
68-
status_response = {
69-
'executionToken': item['executionToken'],
70-
'status': item['status'],
71-
'currentStep': item.get('currentStep'),
72-
'createdAt': item['createdAt'],
73-
'lastUpdated': item['lastUpdated']
74-
}
75-
76-
# Include error if present
77-
if 'error' in item:
78-
status_response['error'] = item['error']
79-
80-
# Include webhook payload summary (not full payload for security)
81-
if 'webhookPayload' in item:
82-
payload = item['webhookPayload']
83-
status_response['webhookSummary'] = {
84-
'type': payload.get('type', payload.get('event', 'unknown')),
85-
'source': payload.get('source', payload.get('sender', 'unknown')),
86-
'keys': list(payload.keys())[:10] # Limit to first 10 keys
87-
}
88-
89-
print(f"Status retrieved successfully: {execution_token}, status: {item['status']}")
90-
91-
return {
92-
'statusCode': 200,
93-
'headers': {
94-
'Content-Type': 'application/json',
95-
'Access-Control-Allow-Origin': '*'
96-
},
97-
'body': json.dumps(status_response)
98-
}
99-
100-
except Exception as e:
101-
print(f"Error querying status: {str(e)}")
102-
return {
103-
'statusCode': 500,
104-
'headers': {
105-
'Content-Type': 'application/json',
106-
'Access-Control-Allow-Origin': '*'
107-
},
108-
'body': json.dumps({
109-
'error': 'Internal server error',
110-
'message': str(e)
111-
})
112-
}
1+
"""
2+
Status Query Function
3+
Retrieves webhook processing status from DynamoDB
4+
"""
5+
import json
6+
import os
7+
from typing import Dict, Any
8+
import boto3
9+
from boto3.dynamodb.conditions import Key
10+
11+
dynamodb = boto3.resource('dynamodb')
12+
table = dynamodb.Table(os.environ['EVENTS_TABLE_NAME'])
13+
14+
15+
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
16+
"""
17+
Query webhook processing status
18+
19+
Args:
20+
event: API Gateway event with executionToken in path parameters
21+
context: Lambda context
22+
23+
Returns:
24+
HTTP response with status information
25+
"""
26+
print(f"Status query event: {json.dumps(event)}")
27+
28+
# Extract execution token from path parameters
29+
execution_token = event.get('pathParameters', {}).get('executionToken')
30+
31+
if not execution_token:
32+
return {
33+
'statusCode': 400,
34+
'headers': {
35+
'Content-Type': 'application/json',
36+
'Access-Control-Allow-Origin': '*'
37+
},
38+
'body': json.dumps({
39+
'error': 'Missing executionToken in path'
40+
})
41+
}
42+
43+
print(f"Querying status for execution token: {execution_token}")
44+
45+
try:
46+
# Query DynamoDB for the execution token
47+
response = table.get_item(
48+
Key={'executionToken': execution_token}
49+
)
50+
51+
if 'Item' not in response:
52+
print(f"Execution token not found: {execution_token}")
53+
return {
54+
'statusCode': 404,
55+
'headers': {
56+
'Content-Type': 'application/json',
57+
'Access-Control-Allow-Origin': '*'
58+
},
59+
'body': json.dumps({
60+
'error': 'Execution token not found',
61+
'executionToken': execution_token
62+
})
63+
}
64+
65+
item = response['Item']
66+
67+
# Build response
68+
status_response = {
69+
'executionToken': item['executionToken'],
70+
'status': item['status'],
71+
'currentStep': item.get('currentStep'),
72+
'createdAt': item['createdAt'],
73+
'lastUpdated': item['lastUpdated']
74+
}
75+
76+
# Include error if present
77+
if 'error' in item:
78+
status_response['error'] = item['error']
79+
80+
# Include webhook payload summary (not full payload for security)
81+
if 'webhookPayload' in item:
82+
payload = item['webhookPayload']
83+
status_response['webhookSummary'] = {
84+
'type': payload.get('type', payload.get('event', 'unknown')),
85+
'source': payload.get('source', payload.get('sender', 'unknown')),
86+
'keys': list(payload.keys())[:10] # Limit to first 10 keys
87+
}
88+
89+
print(f"Status retrieved successfully: {execution_token}, status: {item['status']}")
90+
91+
return {
92+
'statusCode': 200,
93+
'headers': {
94+
'Content-Type': 'application/json',
95+
'Access-Control-Allow-Origin': '*'
96+
},
97+
'body': json.dumps(status_response)
98+
}
99+
100+
except Exception as e:
101+
print(f"Error querying status: {str(e)}")
102+
return {
103+
'statusCode': 500,
104+
'headers': {
105+
'Content-Type': 'application/json',
106+
'Access-Control-Allow-Origin': '*'
107+
},
108+
'body': json.dumps({
109+
'error': 'Internal server error'
110+
})
111+
}

0 commit comments

Comments
 (0)