EventBridge Pipes: connecting services without glue code
Point-to-point integrations with filtering and enrichment, minus the Lambda plumbing.
I used to have a Lambda whose entire job was to read from an SQS queue, reshape the message, and call a Step Functions execution. No business logic, pure plumbing. I had a dozen of these glue Lambdas scattered across the account, each one a thing to deploy, monitor, patch, and pay for cold starts on. EventBridge Pipes deleted most of them.
Pipes is a point-to-point integration: one source, one target, with optional filtering and enrichment in between, and no code to maintain for the wiring itself. Here's where it fits and where it doesn't.
The four stages of a pipe
Every pipe is the same shape, and understanding the stages is most of the learning curve:
- Source, a polling source: SQS, Kinesis, DynamoDB Streams, Amazon MQ, or Kafka.
- Filter, an optional pattern; events that don't match are discarded without cost beyond the read.
- Enrichment, optional call to a Lambda, Step Functions, API Gateway, or API destination to augment the event.
- Target, where matching events go: Step Functions, SQS, SNS, Lambda, EventBridge bus, and many more.
The filter and a transformation step replace exactly the kind of code my glue Lambdas were running.
Filtering without a Lambda
The biggest immediate win is filtering. Instead of a Lambda that reads every message and drops the ones it doesn't care about, the pipe's filter does it before anything downstream runs. The pattern syntax matches EventBridge rules:
{
"Filter": {
"FilterCriteria": {
"Filters": [
{
"Pattern": "{ \"body\": { \"eventType\": [\"order.placed\"], \"amount\": [{ \"numeric\": [\">\", 100] }] } }"
}
]
}
}
}
Only orders over $100 of type order.placed proceed. Everything else is filtered out, and you don't pay for an invocation that does nothing.
Defining a pipe in Terraform
A pipe is a single resource. This one moves filtered SQS messages straight into a Step Functions state machine, the exact job my glue Lambda used to do:
resource "aws_pipes_pipe" "orders_to_sfn" {
name = "orders-to-fulfillment"
role_arn = aws_iam_role.pipe_role.arn
source = aws_sqs_queue.orders.arn
target = aws_sfn_state_machine.fulfillment.arn
source_parameters {
sqs_queue_parameters {
batch_size = 10
}
}
target_parameters {
step_function_state_machine_parameters {
invocation_type = "FIRE_AND_FORGET"
}
}
}
Pipes is for point-to-point: exactly one source to one target. The moment you need fan-out to many independent consumers, that's an EventBridge bus, not a pipe. Use a pipe to feed the bus if you need both.
Enrichment when the event isn't enough
Sometimes the source event lacks a field the target needs, say a DynamoDB stream record has a customer ID but the target wants the full customer profile. The enrichment stage calls out to fetch it. You still write that one small enrichment Lambda, but it's now purely "look up and return," with no polling loop, no batching logic, and no target-invocation code. Pipes owns the transport.
When Pipes is the wrong tool
I don't use Pipes for everything. It's the wrong fit when:
- You need fan-out to multiple targets, use an EventBridge bus with multiple rules.
- The transformation is complex, heavy branching logic belongs in a Lambda or Step Functions, not a transformer template.
- Your source is a custom application event rather than a queue or stream, that's a native EventBridge bus use case.
The decision rule I use: if the answer to "what code does this need?" is "just reshape and forward," it's a pipe. If there's real logic, keep the compute.
Takeaways
- EventBridge Pipes replaces single-purpose glue Lambdas that read a queue, filter, and forward.
- The filter stage drops unwanted events before any downstream cost, no invocation for messages you ignore.
- Pipes is strictly point-to-point; reach for an EventBridge bus when you need fan-out to many consumers.
- Keep enrichment Lambdas small and logic-free; let the pipe own polling, batching, and target invocation.