FREE .NET Web API Course! Join Now 🚀

16 min read

AWS Step Functions with Lambda for .NET Workflows

#dotnet #aws

When you’re building serverless apps with AWS Lambda in .NET, things can get messy fast—especially when the logic spans multiple steps, conditions, or services. Chaining Lambda functions manually or stuffing too much logic into one function becomes hard to manage, test, and scale.

That’s where AWS Step Functions come in.

Step Functions let you orchestrate multiple Lambda functions into clear, reliable workflows—without writing glue code or managing state yourself. You define the flow, AWS handles the rest.

In this guide, you’ll learn how Step Functions work, why they matter for .NET devs, and how to integrate them into real-world serverless workflows using C#.

What Are AWS Step Functions?

AWS Step Functions is a serverless workflow service that lets you coordinate multiple AWS services, including Lambda functions, into defined, visual workflows called state machines.

Instead of writing all logic inside a single Lambda function, Step Functions let you:

  • Break down logic into smaller steps
  • Run steps sequentially, in parallel, or conditionally
  • Handle retries, timeouts, and error paths without extra code
  • Visualize the workflow in the AWS Console

Think of it like a workflow orchestrator. You define the flow (states, transitions, error handling) in JSON or YAML, and Step Functions manage the execution—triggering your Lambda functions in the correct order.

AWS Lambda has a hard timeout limit of 15 minutes. That’s a problem for long-running workflows like video processing, large file imports, or complex business transactions.

To work around this, you can break the job into smaller, manageable units of work—each handled by a separate Lambda function. AWS Step Functions then orchestrate these functions in sequence, parallel, or based on conditions, without hitting the timeout limit.

Step Functions let you:

  • Chain multiple Lambdas to form a complete workflow
  • Persist state between steps
  • Handle retries, timeouts, and fallbacks natively
  • Run workflows that span hours, days, or even longer

This turns Lambda into a powerful orchestrator for long-running, stateful workflows—especially useful in .NET apps where splitting responsibilities into clear service layers already aligns with clean architecture.

Here are the common use cases:

  • Order processing pipelines
  • ETL jobs
  • Microservices orchestration
  • Human approval workflows
  • Basically, any long running jobs.

For .NET developers, Step Functions allow cleaner separation of concerns and reduce complexity inside individual Lambda functions.

Why Use Step Functions with AWS Lambda?

AWS Lambda is great for short, stateless functions. But real-world workflows aren’t always that simple.

Here’s where Step Functions level up your architecture:

  • Separation of Concerns
    Break complex logic into smaller, focused Lambda functions. Each does one job. Step Functions handle the orchestration.
  • Built-in Retry & Error Handling
    No need to write retry logic in your code. Step Functions let you define retries, backoffs, and fallback states declaratively.
  • Visual Workflows
    See exactly how your workflow runs in the AWS Console. Makes debugging and collaboration easier.
  • Less Boilerplate
    Skip writing coordination logic (chaining, conditions, loops). Step Functions take care of execution flow.
  • Parallel & Conditional Execution
    Run steps in parallel or based on conditions without writing a single if-else.
  • Audit & History Tracking
    Every execution is logged with a full history of states. Useful for debugging and compliance.

In short: Step Functions let you build cleaner, more reliable, maintainable workflows—especially when working with multiple Lambda functions or external services.

Core Components of AWS Step Functions

AWS Step Functions is a serverless orchestration service that lets you build workflows using state machines. At its core, a State Machine defines the workflow in JSON using Amazon States Language (ASL). It describes how data moves from one step to another, what work gets done, and how the workflow handles errors or branching.

Each State represents a step in the workflow. Step Functions supports several state types like Task (performs work such as invoking a Lambda or a container task), Choice (for conditional logic), Parallel (runs branches concurrently), Wait (introduces delays), Pass (passes input to output unchanged), and Succeed/Fail to explicitly end workflows. These states are connected using Transitions, which determine the path the execution takes after each state finishes.

Step Functions work on JSON input/output. Each state can filter or transform the data using InputPath, ResultPath, and OutputPath, making it easy to control what gets passed between steps. Every time a workflow runs, it’s called an Execution, which can be triggered via SDK, Lambda, API Gateway, EventBridge, or any AWS-supported integration.

For tasks outside AWS, Step Functions support Activities. This lets a .NET service poll for work from the state machine, process it, and return results. While less common now due to Lambda, it’s useful for legacy systems or compute-heavy .NET apps.

Error handling is built-in with Retry and Catch blocks. You can configure retries on specific exceptions, set backoff strategies, or move to fallback states on failure. Combined with Timeouts, you get full control over reliability.

Finally, Step Functions integrates with CloudWatch Logs and X-Ray, making it easier to trace and debug complex workflows. For .NET developers building microservices or background workflows, this brings robust orchestration without writing custom state handling or error retry logic.

Here’s a rewritten, dev-focused version that fits naturally into your article and speaks directly to .NET developers using Step Functions:


Why Step Functions are Awesome?

Resilient by default
Step Functions handle retries, timeouts, and failure recovery out of the box. You can configure retry policies, catch specific exceptions, and define fallback logic—without wrapping every Lambda in try-catch blocks. Whether your task runs for 3 seconds or 3 days, Step Functions keep track of its state and resume exactly where it left off.

Built-in observability
Every execution has a full event history—visually and programmatically. You get timestamps, input/output per step, and failure traces. No custom logging hacks. Just open the console and you’ll instantly see which step failed and why.

Effortless scaling
As the load on your system increases, Step Functions scale with it—automatically. You don’t manage queues, workers, or parallelism. Whether you trigger one registration or a thousand, the orchestration layer just handles it.

Highly available
Step Functions are fault-tolerant by design. They run across multiple AZs in every region, so your workflow isn’t affected by instance-level or data center failures. No need to worry about uptime, patching, or cluster management.

Cost-efficient
You pay only for what you use. Pricing is based on state transitions, not execution time or Lambda duration. Even if your workflow spans hours, you’re only billed per step, making it cost-effective for both short and long-running workflows.

Tight IAM integration
With IAM, you can lock down exactly who can start executions, modify workflows, or invoke specific Lambdas. Granular access control ensures your workflows are secure and traceable—right out of the box.

Workflow Studio You can build your workflows faster using the comprehensive Workflow Studio UI of Step Functions.

Understanding States in Step Functions

In AWS Step Functions, the “state” of a workflow refers to the current step being executed, along with the data (also called the execution context) that’s passed through each state. The state machine keeps track of which state it’s in, what input it received, and what output it needs to pass to the next state. Each state processes input data, optionally transforms it, and forwards the result to the next step, allowing you to chain tasks together with clean data flow.

The core mechanism that enables this parameter passing is JSON-based input/output manipulation. Each state can define four key properties to control this: InputPath, Parameters, ResultPath, and OutputPath. InputPath selects a portion of the input to be passed to the state. Parameters can be used to reshape or enrich the input — for example, by injecting static values or mapping specific fields. After the task executes, the result can be merged with the original input using ResultPath. Finally, OutputPath defines what part of the combined result should be sent to the next state.

This approach gives you fine-grained control over what data flows between each step. You can pass only what’s needed, inject static values, or completely reshape the payload. For example, you might receive a large JSON input in the first state, extract just one field using InputPath, call a Lambda with Parameters, store the result using ResultPath, and finally send only a small portion of that data to the next state with OutputPath.

As the state machine runs, the execution context is passed forward and mutated step by step. This means that unless you overwrite it with ResultPath: null, the output of one state becomes the input of the next. States like Choice, Map, or Parallel also manipulate the flow but maintain isolation in how they handle input/output within their branches or iterations.

The state machine’s backend takes care of maintaining this state context between steps, even if the workflow spans hours or days. It tracks progress persistently — so if a task fails or times out, you can retry from that specific state rather than starting over. This built-in state management, combined with flexible data flow tools, is what makes Step Functions powerful for orchestrating complex workflows with minimal glue code.

What we’ll build?

User Registration Workflow with Step Functions and .NET Lambda

Enough of theory. Let’s jump into some actions.

To demonstrate how AWS Step Functions can orchestrate .NET Lambda functions, let’s build a simple yet realistic user registration workflow. This mirrors a common pattern in modern applications, where multiple steps must happen in a specific sequence—and each step needs to handle failure gracefully.

  • ValidateUserInput
    – Validates required fields (email, password, etc.)
  • CreateUserRecord
    – Creates a user in DB (DynamoDB, SQL, etc.)
  • SendVerificationEmail
    – Sends confirmation email to the user

This workflow simulates a typical user registration process, where each step is handled by a dedicated .NET Lambda. The flow starts with ValidateUserInput, which checks if the incoming registration data is clean—valid email, strong password, and no duplicates. If the input is invalid, we don’t continue further.

If validation passes, we move to CreateUserRecord, which inserts the new user into a database. This could be anything—DynamoDB, SQL Server, or a microservice.

Next, we call SendVerificationEmail. This Lambda sends a verification link or welcome email. Email systems are external and flaky, so this is where Step Functions’ built-in retry policies really shine. You can define how many times to retry and how long to wait—all without writing retry logic in code.

Writing the Lambda Code

Let’s first build up our Sample .NET Lambdas.

Open up Visual Studio, and create a new solution to house all the required Lambdas. I named my solution as StepFunctions.Demo.

Validate User Input Lambda

Here is the first Lambda that can validate the incoming user input.

using Amazon.Lambda.Core;
using System.Text.RegularExpressions;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace ValidateUserInput;
public class Function
{
public async Task<LambdaResponse> FunctionHandler(UserInput input, ILambdaContext context)
{
if (string.IsNullOrWhiteSpace(input.Email) || string.IsNullOrWhiteSpace(input.Password))
throw new Exception("Email and Password are required.");
if (!IsValidEmail(input.Email))
throw new Exception("Invalid email format.");
if (input.Password.Length < 6)
throw new Exception("Password must be at least 6 characters.");
return new LambdaResponse("Validation passed", input.Email, input.Password);
}
private bool IsValidEmail(string email)
{
return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}
}
public record UserInput(string Email, string Password);
public record LambdaResponse(string Message, string Email, string Password);

This Lambda validates user input for registration. It checks if email and password are present, ensures the email format is valid, and enforces a minimum password length. If all checks pass, it returns a success message; otherwise, it throws an exception to trigger failure handling in the Step Function.

User Creation Lambda

Next, let’s simulate user creation. You can use datastores like DynamoDB for this. In the demonstration I am not essentially writing to any databases.

using Amazon.Lambda.Core;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace UserCreation;
public class Function
{
public async Task<LambdaResponse> FunctionHandler(UserInput input, ILambdaContext context)
{
// Simulate DB write
var userId = Guid.NewGuid().ToString();
return new LambdaResponse($"User created with ID : {userId}", input.Email, userId);
}
}
public record UserInput(string Email, string Password);
public record LambdaResponse(string Message, string Email, string? UserId = null);

This Lambda simulates user creation by generating a new GUID as the user ID. It mimics a database insert and returns a success message along with the generated user ID. No actual persistence is done, since this is for demo purposes.

Send verification Email Lambda

So, once the user creation request is validated and user is created in our database, we need to send the user a verification email to confirm the user’s email address.

using Amazon.Lambda.Core;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace SendVerificationEmail;
public class Function
{
public async Task<LambdaResponse> FunctionHandler(UserInput input, ILambdaContext context)
{
// Simulate email sending delay
await Task.Delay(500);
return new LambdaResponse("Verification email sent");
}
}
public record UserInput(string Email);
public record LambdaResponse(string Message);

This Lambda simulates sending a verification email by introducing a short delay. It takes user input (mainly the email), pretends to send an email, and returns a success message—ideal for mocking email delivery in a Step Function demo.

Deploying all the .NET Lambdas

I got all these Lambdas deployed to AWS directly via my Visual Studio IDE. You can simply right-click the C# Project, and hit on Publish to AWS Lamdbda

deploy

deploy

And here are all my Lambdas.

deploy

Now that we have all the required Lambdas uploaded to the AWS Cloud, let’s explore AWS Step Functions.

AWS Step Functions : Walkthrough

Open up AWS Management Console and open up Step Functions. Click on Get Started.

step-functions

I usually recommend NOT using the AWS Management Console for resource creation, and to rely on IaC tools like Terraform to provision and manage resources the right way. However, Step Functions is one of the few services where the console experience is actually great. The UI is clean, intuitive, and makes it easy to visualize, build, and test workflows—especially when you’re getting started or building quick demos.

First up, let’s create a new State Machine. I named my State Machine as “sample”.

state-machine

We’ll select the machine type as Standard, and click on continue.

Now we need to design the workflow that we need.

state-machine

As mentioned earlier, Step Functions are basically a way to orchestrate serverless components and build a sophisticated event driven system. Hence, you can see in the above screenshot that you will be able to add services like AWS Lambda, Publish SNS Topics, Run ECS Tasks, and even trigger another AWS Step Function. The possibilities are endless.

But for this demonstration we will keep it simple and stick to invoking Lambdas only. In an another article, I will demonstrate a even more complex use case, where we will build a significantly advanced workflow with step functions.

Since we are aware that there are 3 Lambda Functions in the picture, let’s drag and drop Invoke AWS Lambda 3 times into the workflow chart.

Let’s configure our first Lambda Step.

state-machine

I named the first step as “Validation”, and pointed it to the validate-user-input Lambda. Note that we need to set the Payload as Use state input as payload.. This means that whatever input we give to the Step Function, will be passed on to this Validation Lambda Step. You can also give custom payloads, or even completely remove all the payloads if you want.

Let’s configure our next step, which I have named it as User Creation.

state-machine

Here, I have pointed to the user-creation function.

state-machine

The final step will point to the send-verification-mail Lambda function.

Make sure all these steps use the payload from the state machine. Only this way you can ensure that the parameters flow through each step and is available in the next steps.

With that done, save your State Machine. That’s it!

Testing

Click on Execute to test the workflow.

To start execution, we should provide an input JSON payload. In our case, we have to provide an email id and password which will be then processed by our state machine.

{
"Email": "[email protected]",
"Password": "test123"
}

I am passing this payload, and clicking on Start Execution. This creates a new execution.

state-machine

As you can see, all the steps have executed as expected. You can also see the input and outputs of each steps. This can be helpful while debugging the workflow.

state-machine

It also gives you the sequence of events that occured in your workflow. Along with this you can also refer to the logs from cloudwatch per lambda.

How are Step Functions Invoked?

AWS Step Functions can be invoked in several ways, depending on the source of the trigger and the architecture of your application. Below is a breakdown of the common methods used to start a Step Function execution.


1. Using AWS SDK (from code)

You can invoke a Step Function from your application using the AWS SDK. This works across supported languages like C#, Python, JavaScript, etc.

C# Example using AWS SDK for .NET:

var client = new AmazonStepFunctionsClient();
var request = new StartExecutionRequest
{
StateMachineArn = "arn:aws:states:region:account:stateMachine:YourStateMachineName",
Input = "{\"key1\": \"value1\"}"
};
var response = await client.StartExecutionAsync(request);
Console.WriteLine("Execution ARN: " + response.ExecutionArn);

All you need is the State Machine ARN and a valid JSON input.


2. Using AWS Console

You can start an execution manually from the AWS Management Console:

  • Go to the Step Functions service.
  • Select your state machine.
  • Click “Start execution”.
  • Provide JSON input and click “Start”.

This method is useful for quick testing and debugging.


3. From AWS Lambda

Step Functions can be invoked from a Lambda function, which is common in event-driven workflows.


4. Triggered by EventBridge

You can use EventBridge (formerly CloudWatch Events) to trigger Step Functions based on:

  • Scheduled intervals (cron expressions)
  • S3 events
  • DynamoDB streams
  • Custom application events

This is ideal for automating workflows without writing additional code.


5. From API Gateway (as an HTTP endpoint)

You can expose a Step Function as a REST API using API Gateway. This is done by:

  • Creating an API in API Gateway
  • Using Lambda proxy integration or direct integration to call StartExecution

Useful when external systems or frontends need to trigger workflows via HTTP.


6. From Another Step Function

Step Functions support nested workflows. You can call one state machine from another using a StartExecution task type. This is useful for breaking down complex flows into reusable subflows.


7. Using AWS CLI

You can start a Step Function execution via the command line for scripting or manual execution.

Terminal window
aws stepfunctions start-execution \
--state-machine-arn arn:aws:states:region:account:stateMachine:YourStateMachine \
--input '{"foo":"bar"}'

Summary

In this article, we covered the fundamentals of how AWS Step Functions work, with a focus on understanding states, how data flows through each step using input/output manipulation, and the various ways to invoke a state machine—whether from code, Lambda, EventBridge, API Gateway, or the console. You now have a solid grasp of how Step Functions orchestrate workflows and manage execution context across multiple states.

In the next article, we’ll take it a step further and build a more complex workflow that includes error handling, retries, fallbacks, and advanced features like nested workflows and dynamic parallel execution. You’ll learn how to design robust and production-ready orchestrations using AWS Step Functions.

If you found this helpful, share it with your network so more people can benefit. Let’s build better workflows together.

✨ Grab the Source Code!

Access the full implementation and learn how everything works under the hood. Don't forget to star my GitHub repo if you find it helpful!

Support ❤️
If you have enjoyed my content, support me by buying a couple of coffees.
Share this Article
Share this article with your network to help others!
What's your Feedback?
Do let me know your thoughts around this article.

Level Up Your .NET Skills

Join my community of 8,000+ developers and architects.
Each week you will get 1 practical tip with best practices and real-world examples.