Repository navigation
lambda webhooks
This document describes how to configure and use AWS Lambda functions as webhook endpoints for Pusher events in the soketi-rs server.
The soketi-rs server supports invoking AWS Lambda functions as webhook endpoints, providing a serverless alternative to traditional HTTP webhooks. Lambda webhooks support:
- Async invocation (Event): Fire-and-forget invocation that doesn't wait for a response
- Sync invocation (RequestResponse): Waits for the Lambda function to complete and returns the response
- Client context: Pass custom metadata to Lambda functions
- Channel filtering: Filter events by channel name prefix/suffix
- Batching: Batch multiple events together before invoking Lambda
To configure a Lambda webhook, set the lambda_function field in the webhook configuration:
use soketi_rs::app::{App, Webhook, LambdaConfig};
let mut app = App::new(
"app-id".to_string(),
"app-key".to_string(),
"app-secret".to_string(),
);
app.webhooks = vec![Webhook {
url: None, // No HTTP URL
lambda_function: Some("my-lambda-function".to_string()),
event_types: vec!["channel_occupied".to_string()],
headers: None,
filter: None,
lambda: Some(LambdaConfig {
client_context: None,
async_invocation: true,
}),
}];Async invocation is fire-and-forget. The Lambda function is invoked asynchronously and the server doesn't wait for a response:
lambda: Some(LambdaConfig {
client_context: None,
async_invocation: true, // Fire-and-forget
})Use cases:
- High-throughput event processing
- Non-critical notifications
- Background processing
- When you don't need to know if the Lambda succeeded
Sync invocation waits for the Lambda function to complete and returns the response:
lambda: Some(LambdaConfig {
client_context: None,
async_invocation: false, // Wait for response
})Use cases:
- Critical event processing
- When you need to know if the Lambda succeeded
- When you need the Lambda's response
- Lower-throughput scenarios
You can pass custom metadata to Lambda functions using client context:
use serde_json::json;
lambda: Some(LambdaConfig {
client_context: Some(json!({
"custom": {
"app_id": "my-app",
"environment": "production",
"region": "us-east-1"
}
})),
async_invocation: true,
})The client context is available in the Lambda function's context object.
Lambda webhooks support channel name filtering:
use soketi_rs::app::WebhookFilter;
Webhook {
url: None,
lambda_function: Some("my-function".to_string()),
event_types: vec!["channel_occupied".to_string()],
headers: None,
filter: Some(WebhookFilter {
channel_name_starts_with: Some("private-".to_string()),
channel_name_ends_with: Some("-notifications".to_string()),
}),
lambda: Some(LambdaConfig {
client_context: None,
async_invocation: true,
}),
}This webhook will only be invoked for channels that start with private- and end with -notifications.
Lambda webhooks support all Pusher event types:
-
channel_occupied: When a channel becomes occupied (0 → 1+ subscribers) -
channel_vacated: When a channel becomes vacated (1+ → 0 subscribers) -
member_added: When a member joins a presence channel -
member_removed: When a member leaves a presence channel -
client_event: When a client sends an event -
cache_miss: When a cache miss occurs on a cache-enabled channel
Example with multiple event types:
Webhook {
url: None,
lambda_function: Some("my-function".to_string()),
event_types: vec![
"channel_occupied".to_string(),
"channel_vacated".to_string(),
"member_added".to_string(),
"member_removed".to_string(),
],
headers: None,
filter: None,
lambda: Some(LambdaConfig {
client_context: None,
async_invocation: true,
}),
}Lambda functions receive a JSON payload with the following structure:
{
"time_ms": 1234567890123,
"events": [
{
"name": "channel_occupied",
"channel": "my-channel",
"event": null,
"data": null,
"socket_id": null,
"user_id": null
}
]
}-
time_ms: Unix timestamp in milliseconds when the webhook was sent -
events: Array of events (may contain multiple events if batching is enabled)-
name: Event type (e.g., "channel_occupied", "member_added") -
channel: Channel name -
event: Event name (for client_event only) -
data: Event data (for client_event only) -
socket_id: Socket ID (for client_event only) -
user_id: User ID (for member events and client_event on presence channels)
-
Lambda webhooks support batching multiple events together:
use soketi_rs::webhook_sender::{WebhookSender, BatchingConfig};
let batching_config = BatchingConfig {
enabled: true,
duration_ms: 100, // Batch events for 100ms
};
let sender = WebhookSender::with_config(batching_config, true).await;When batching is enabled:
- Events are collected for the specified duration
- Multiple events are sent in a single Lambda invocation
- The
eventsarray in the payload will contain multiple events - Reduces Lambda invocation costs for high-throughput scenarios
use soketi_rs::webhook_sender::{WebhookSender, BatchingConfig};
let batching_config = BatchingConfig {
enabled: false,
duration_ms: 50,
};
// Initialize with Lambda support (uses default AWS config from environment)
let sender = WebhookSender::with_config(batching_config, true).await;use soketi_rs::webhook_sender::WebhookSender;
// Create custom AWS config and Lambda client
let aws_config = aws_config::load_from_env().await;
let lambda_client = aws_sdk_lambda::Client::new(&aws_config);
// Create WebhookSender with custom Lambda client
let sender = WebhookSender::new()
.with_lambda_client(lambda_client);The Lambda client requires AWS credentials to be configured. You can provide credentials using:
-
Environment variables:
export AWS_ACCESS_KEY_ID=your_access_key export AWS_SECRET_ACCESS_KEY=your_secret_key export AWS_REGION=us-east-1
-
AWS credentials file (
~/.aws/credentials):[default] aws_access_key_id = your_access_key aws_secret_access_key = your_secret_key
-
AWS config file (
~/.aws/config):[default] region = us-east-1
-
IAM role (when running on EC2, ECS, or Lambda)
The AWS credentials must have permission to invoke Lambda functions. Required IAM policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction",
"lambda:InvokeAsync"
],
"Resource": "arn:aws:lambda:*:*:function:*"
}
]
}For production, restrict the Resource to specific Lambda function ARNs.
You can configure both HTTP and Lambda webhooks for the same app:
app.webhooks = vec![
Webhook {
url: Some("https://example.com/webhook".to_string()),
lambda_function: None,
event_types: vec!["channel_occupied".to_string()],
headers: None,
filter: None,
lambda: None,
},
Webhook {
url: None,
lambda_function: Some("my-lambda-function".to_string()),
event_types: vec!["channel_occupied".to_string()],
headers: None,
filter: None,
lambda: Some(LambdaConfig {
client_context: None,
async_invocation: true,
}),
},
];Both webhooks will receive the same events.
Here's an example Lambda function (Node.js) that processes webhook events:
exports.handler = async (event) => {
console.log('Received webhook:', JSON.stringify(event, null, 2));
const { time_ms, events } = event;
for (const evt of events) {
console.log(`Event: ${evt.name}, Channel: ${evt.channel}`);
switch (evt.name) {
case 'channel_occupied':
// Handle channel occupied
console.log(`Channel ${evt.channel} is now occupied`);
break;
case 'channel_vacated':
// Handle channel vacated
console.log(`Channel ${evt.channel} is now vacated`);
break;
case 'member_added':
// Handle member added
console.log(`User ${evt.user_id} joined ${evt.channel}`);
break;
case 'member_removed':
// Handle member removed
console.log(`User ${evt.user_id} left ${evt.channel}`);
break;
case 'client_event':
// Handle client event
console.log(`Client event ${evt.event} on ${evt.channel}`);
break;
}
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'Webhook processed successfully' })
};
};Lambda invocation errors are logged but don't block the webhook sender:
// If Lambda invocation fails, an error is logged
Log::error(&format!("Lambda webhook failed: {}", e));For async invocations, errors are not returned to the caller. For sync invocations, errors are logged but the webhook sender continues processing.
- Use async invocation for high-throughput scenarios to avoid blocking
- Use sync invocation for critical events where you need to know if processing succeeded
- Enable batching to reduce Lambda invocation costs
- Use channel filtering to reduce unnecessary Lambda invocations
- Set appropriate IAM permissions to restrict Lambda access
- Monitor Lambda metrics (invocations, errors, duration) in CloudWatch
- Use client context to pass app-specific metadata to Lambda functions
- Test Lambda functions with sample payloads before deploying
- Check AWS credentials are configured correctly
- Verify IAM permissions allow Lambda invocation
- Check Lambda function name is correct
- Verify event types match the events being triggered
- Check channel filters if configured
- Check CloudWatch logs for Lambda function errors
- Verify Lambda function has correct permissions
- Check Lambda function timeout settings
- Verify payload format is correct
- Use async invocation for non-critical events
- Enable batching to reduce invocation count
- Optimize Lambda function cold start time
- Use provisioned concurrency for consistent performance