Lambda Durable Functions: how to test callbacks


Lambda Durable Functions comes with a handy testing SDK. It makes it easy to test durable executions both locally as well as remotely in the cloud.

I find the local test runner particular useful for dealing with wait states because I can simply configure the runner to skip time!

However, this does not work for callback operations such as waitForCallback. Unfortunately, the official docs didn't include any examples on how to handle this.

So here's my workaround.

The handler code

Imagine you're implement an order processing workflow for a food delivery service.

When an order comes in, you would:

1) save the order details in the database.

2) publish an "order_placed" event to EventBridge.

3) send a notification to the restaurant and wait for them to confirm whether they accept the order or not.

This last step is very important, and there are many reasons for a restaurant to reject an order. They might have run out of ingredients for the dish. Or maybe they are overbooked and don't have the bandwidth to take the order.

Equally, we need to put a timeout on the notification because we can't keep the customer waiting forever.

So your handler code might look something like this:

The notifyRestaurant function will send a push notification to the restaurant's POS device with the relevant information about the order.

We want to test the three possible scenarios here:

  • order is accepted
  • order is rejected
  • order timed out

The test code

The first challenge with testing callbacks is that, if you follow all the online examples with:

Then the execution will hang as it waits for the callback.

Unfortunately, setting "skipTime" to true doesn't help here. Because it only applies to wait operations, not callbacks.

Instead, you must start the execution and let it run in the background (without awaiting). For example...

The next challenge is that, if you try to fetch the "Notify restaurant" callback operation right away, it might not be available yet. This might be because the preceding operations have not completed yet.

In that case, the following lines to fetch the "Notify restaurant" operation will throw an error.

If that's the case, you might have to wait a bit and let the execution reach the "Notify restaurant" step first.

Here's what that might look like.

So that covers the test cases for the restaurant accepting and rejecting the order. But what about the timeout path? We wouldn't want to wait 10 mins for the callback to timeout naturally.

In this specific case, where:

  1. The durable execution SDK does not have an error type for timeouts. ps. I have opened a feature request for this, if you want to chip in.
  2. The handler code is not expecting any failure response from the callback, so it treats all errors as timeout.

So to trigger the timeout path, we can send a failure response to the callback to drive the execution towards the timeout path.

This feels like a hack, but it's the best I have come up with. Please let me know if you know of a better way to do this!

What can be improved?

AWS has done a great job with Lambda Durable Functions and the testing SDK is generally easy to use. But as you have seen, testing callbacks is still tricky.

I have shown you my workaround but it's not a satisfying solution.

It'd be much better if I can provide a delegate function to the LocalDurableTestRunner to provide the callback when requested.

For example, maybe something like this:

Again, if you can think of something better, please let me know!

Master Serverless

Join 17K readers and level up you AWS game with just 5 mins a week.

Read more from Master Serverless

Lambda Durable Functions is a powerful new feature, but its checkpoint + replay model has a few gotchas. Here are five to watch out for. Non-deterministic code The biggest gotcha is when the code is not deterministic. That is, it might do something different during replay. Remember, when a durable execution is replayed, the handler code is executed from the start. So the code must behave exactly the same given the same input. If you use random numbers, or timestamps to make branching...

Hi, I have just finished adding some content around Lambda Managed Instances (LMI) to my upcoming workshop. I put together a cheatsheet of the important ways that LMI is different from Lambda default and thought maybe you'd find it useful too. You can also download the PDF version below. Lambda default vs. Lambda managed instances.pdf

Two weeks ago, I gave you the biggest serverless announcements pre-re:Invent (see here). So here are the biggest serverless announcements during re:Invent 2025. Lambda Managed Instances Here’s the official announcement. A common pushback against Lambda is that “it’s expensive at scale” because: 1) Each execution environment can only process one request at a time, wasting available CPU cycles while you wait for IO response. 2) Paying for execution time is less efficient when handling thousands...