Skip to content
AWS aws serverless 5 min read

Lambda Triggers & Event Sources

A Lambda function (a small piece of code AWS runs for you) does nothing on its own — something has to invoke it. That “something” is a trigger, and the service that fires the trigger is the event source. This page surveys the most common event sources and, more importantly, explains the three different ways Lambda gets invoked. The way you connect matters because retry and error behavior differ enormously between them — getting this wrong is the single most common reason engineers lose events without noticing.

The three invocation models

Before picking a trigger, you must know how it invokes your function. There are exactly three models, and each handles failure differently.

ModelHow it worksWho uses itRetry behavior on error
Synchronous (push)Caller invokes, waits for the responseAPI Gateway, Application Load Balancer, direct SDK callsNone by Lambda — the caller sees the error and decides what to do
Asynchronous (push)Caller hands the event to Lambda and walks awayS3 events, SNS, EventBridgeLambda retries twice (3 attempts total), then sends to a dead-letter queue (DLQ) if configured
Poll-based (event source mapping)Lambda polls the source and pulls batchesSQS, DynamoDB Streams, Kinesis, Amazon MSK (Managed Streaming for Kafka)Keeps retrying until the message expires or the batch succeeds

A DLQ is a “dead-letter queue” — a holding place for events that failed every attempt, so you can inspect them later instead of losing them.

The big gotcha: Do not assume “Lambda retries for me.” A synchronous caller gets the error thrown straight back and Lambda retries nothing. An asynchronous source retries only twice. A poll-based SQS source can retry the same message for days until it expires. Always confirm which model your trigger uses before you rely on retries.

Push-based sources (Lambda does not poll)

In push-based sources, the event source calls Lambda’s Invoke API directly. You attach a resource-based policy (a permission statement on the function that says “this service is allowed to invoke me”).

API Gateway — synchronous

API Gateway turns an HTTP request into a Lambda invocation and returns your function’s response to the caller. This is synchronous: if your function errors or times out, the user gets a 502 or 504. Use it for REST and HTTP APIs and webhooks. See Build a serverless API for the full setup.

S3 events — asynchronous

S3 (Simple Storage Service, AWS object storage) can invoke a function whenever an object is created or deleted. Use it for processing uploads (resize an image, scan a file). Because it is asynchronous, a failed invoke retries twice then disappears unless you configure a DLQ.

Console steps:

  1. Open the Lambda console and select your function.
  2. Click Add trigger.
  3. Choose S3 as the source.
  4. Pick your Bucket, set Event type to PUT (all object-create events).
  5. (Optional) add a Prefix like uploads/ so only those keys trigger it.
  6. Acknowledge the recursive-invocation warning and click Add.

The CLI equivalent has two parts. First give S3 permission to invoke the function, then wire up the bucket notification:

aws lambda add-permission \
  --function-name process-upload \
  --principal s3.amazonaws.com \
  --statement-id s3invoke \
  --action lambda:InvokeFunction \
  --source-arn arn:aws:s3:::my-upload-bucket-0a1b2c3d \
  --source-account 111122223333

Output:

{
    "Statement": "{\"Sid\":\"s3invoke\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"s3.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:us-east-1:111122223333:function:process-upload\"}"
}
aws s3api put-bucket-notification-configuration \
  --bucket my-upload-bucket-0a1b2c3d \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [{
      "LambdaFunctionArn": "arn:aws:lambda:us-east-1:111122223333:function:process-upload",
      "Events": ["s3:ObjectCreated:Put"]
    }]
  }'

SNS and EventBridge — asynchronous

SNS (Simple Notification Service, a pub/sub messaging service) and EventBridge (a serverless event bus) both push events to Lambda asynchronously. Use SNS for fan-out (one message to many subscribers). Use EventBridge for routing AWS service events or scheduled cron-style jobs. See Amazon SNS and EventBridge.

Poll-based sources (event source mappings)

Here Lambda creates a managed poller that reads from the source in batches and invokes your function. You configure an event source mapping (ESM) — a connection object owned by Lambda. The source never calls Lambda; Lambda pulls from it.

SQS

SQS (Simple Queue Service) is a message queue. Lambda polls it, pulls a batch (up to 10,000 records, default 10), and invokes your function once per batch. If the function throws, the messages become visible again and are retried — until the queue’s retention period expires (up to 14 days). Configure a redrive policy with a maxReceiveCount so poison messages move to a DLQ instead of looping forever.

aws lambda create-event-source-mapping \
  --function-name process-orders \
  --event-source-arn arn:aws:sqs:us-east-1:111122223333:orders-queue \
  --batch-size 10 \
  --function-response-types ReportBatchItemFailures

Output:

{
    "UUID": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "BatchSize": 10,
    "EventSourceArn": "arn:aws:sqs:us-east-1:111122223333:orders-queue",
    "FunctionArn": "arn:aws:lambda:us-east-1:111122223333:function:process-orders",
    "State": "Creating",
    "FunctionResponseTypes": ["ReportBatchItemFailures"]
}

ReportBatchItemFailures lets your function return only the IDs that failed, so successful messages in the batch are not retried. Without it, one bad message reprocesses the whole batch.

DynamoDB Streams and Kinesis

DynamoDB Streams emit a record for every change to a table; Kinesis Data Streams carry high-volume real-time data. Both are ordered per shard, so Lambda processes one batch at a time per shard and, by default, retries a failing batch until it succeeds or the data expires — blocking that shard. Always set MaximumRetryAttempts and an OnFailure destination so a poison record cannot stall the stream. See DynamoDB Streams.

Cost note: Poll-based mappings are billed for the invocations and duration, not the polling itself — SQS/stream polling is free. But a tight retry loop on a poison message can rack up millions of invocations. At roughly $0.20 per million requests plus duration, a stuck DynamoDB shard retrying for hours is a real (and avoidable) bill. Always cap retries.

Best practices

  • Identify the invocation model (sync / async / poll) for every trigger before you write error handling — it dictates everything about retries.
  • Configure a DLQ or OnFailure destination on every asynchronous and stream-based trigger so failed events are captured, not silently dropped.
  • For SQS, set maxReceiveCount on a redrive policy and use ReportBatchItemFailures to avoid reprocessing whole batches.
  • For DynamoDB/Kinesis, cap MaximumRetryAttempts and set BisectBatchOnFunctionError so one bad record cannot poison an entire batch.
  • Keep handlers idempotent (safe to run twice on the same event) — every model can deliver an event more than once.
  • Grant the narrowest permission: scope add-permission to a specific --source-arn, never a wildcard.
  • Watch the IteratorAge (streams) and queue depth (SQS) CloudWatch metrics to catch a stuck or falling-behind consumer early.
Last updated June 15, 2026
Was this helpful?