# Migrating Functions from callback to async/await

Starting with Node.js 24, AWS Lambda no longer supports callback-based function handlers. If you're on Twilio's Runtime Handler 2.1.2 or above, your existing callback-style Functions continue to work on all runtimes without code changes. Twilio recommends migrating to async/await regardless.

> \[!NOTE]
>
> Async/await requires Runtime Handler 2.1.2 or above on all runtimes, including Node.js 22. See the [Runtime Handler](/docs/serverless/functions-assets/handler) page for how to update.

## What is changing

Previously, Twilio Functions used a callback pattern to signal completion. To make an async API call, you had to use `.then()/.catch()` chains:

```js title="Callback-style handler (old)"
exports.handler = (context, event, callback) => {
  const client = context.getTwilioClient();
  client.messages
    .create({ to: event.To, from: event.From, body: 'Hello!' })
    .then((message) => {
      return callback(null, message.sid);
    })
    .catch((error) => {
      return callback(error);
    });
};
```

With async/await, you can use `await` directly inside the handler:

```js title="Async/await handler (new)"
exports.handler = async (context, event, callback) => {
  const client = context.getTwilioClient();
  try {
    const message = await client.messages.create({
      to: event.To,
      from: event.From,
      body: 'Hello!',
    });
    return callback(null, message.sid);
  } catch (error) {
    return callback(error);
  }
};
```

> \[!NOTE]
>
> The `callback` parameter is still available and fully supported. Adding `async` to your handler signature is the minimum change required — it lets you use `await` inside your function. You can continue calling `callback` to return your response, or you can return a value directly and Twilio's runtime-handler will handle it automatically.

## Migration patterns

### Simple synchronous handler

No async operations — just add `async` to the signature.

```js title="Before"
exports.handler = (context, event, callback) => {
  const twiml = new Twilio.twiml.MessagingResponse();
  twiml.message('Hello, World!');
  return callback(null, twiml);
};
```

```js title="After"
exports.handler = async (context, event, callback) => {
  const twiml = new Twilio.twiml.MessagingResponse();
  twiml.message('Hello, World!');
  return callback(null, twiml);
};
```

### Handler with async operations (`.then`/`.catch`)

Replace promise chains with `await` and `try`/`catch`.

```js title="Before"
exports.handler = (context, event, callback) => {
  const client = context.getTwilioClient();
  client.messages
    .create({ to: event.To, from: event.From, body: 'Hello!' })
    .then((message) => {
      return callback(null, message.sid);
    })
    .catch((error) => {
      return callback(error);
    });
};
```

```js title="After"
exports.handler = async (context, event, callback) => {
  const client = context.getTwilioClient();
  try {
    const message = await client.messages.create({
      to: event.To,
      from: event.From,
      body: 'Hello!',
    });
    return callback(null, message.sid);
  } catch (error) {
    return callback(error);
  }
};
```

### Handler returning a value directly

With an async handler, you can also return a value directly without calling `callback`. The runtime-handler detects the returned Promise and handles it automatically.

```js title="Returning a value directly"
exports.handler = async (context, event) => {
  const twiml = new Twilio.twiml.MessagingResponse();
  twiml.message('Hello, World!');
  return twiml;
};
```

> \[!WARNING]
>
> Do not both call `callback` and return a value in the same handler. While the runtime-handler guards against double responses, it is still confusing and error-prone. Pick one approach and use it consistently.

### Handler with error handling

With the callback pattern, errors are passed as the first argument to `callback`. With async/await you can throw an error instead — if you are returning values directly rather than calling `callback`.

```js title="Error handling with callback (still valid)"
exports.handler = async (context, event, callback) => {
  try {
    const result = await someAsyncOperation();
    return callback(null, result);
  } catch (error) {
    return callback(error);
  }
};
```

```js title="Error handling by throwing (when returning directly)"
exports.handler = async (context, event, callback) => {
  const result = await someAsyncOperation(); // throws on failure
  return result;
};
```

## Summary of changes

|                    | Callback style               | Async/await style                         |
| ------------------ | ---------------------------- | ----------------------------------------- |
| Handler signature  | `(context, event, callback)` | `async (context, event, callback)`        |
| Return a response  | `callback(null, twiml)`      | `callback(null, twiml)` or `return twiml` |
| Return an error    | `callback(error)`            | `callback(error)` or `throw error`        |
| Async operations   | `.then().catch()`            | `await` + `try/catch`                     |
| Node.js 24 support | No                           | Yes (requires runtime-handler 2.1.2+)     |

## Additional resources

* [Function execution](/docs/serverless/functions-assets/functions/invocation) — detailed reference for the `context`, `event`, and `callback` parameters
* [Runtime Handler](/docs/serverless/functions-assets/handler) — runtime-handler versions and how to update
* [Node.js v22 upgrade](/docs/serverless/functions-assets/node-upgrade) — upgrading your runtime version
* [AWS Lambda Node.js handler reference](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html#nodejs-handler-callback) — AWS documentation on the callback removal in Node.js 24
