> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ada.cx/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ada.cx/_mcp/server.

# Relaying voice texts through your own SMS provider

A Voice AI Agent texts callers during a call. By default, those text messages leave from the Twilio number configured for your AI Agent. This guide shows how to send them through your own SMS provider and phone number instead, and how to bring the caller's text reply back into the live call.

A Conversations API channel with the `sms` modality carries the text messages. Your relay sends each one with your own SMS provider. When the caller replies by text, your relay posts the reply to Ada, and the AI Agent answers by voice on the call.

Your relay is a service you run. It receives each of the AI Agent's text messages from Ada as a webhook and sends it through your SMS provider. It receives the caller's reply from your provider and posts it to Ada.

> **Tip**
>
> You do not have to write the relay from scratch. Our [reference relay](https://github.com/AdaSupport/ada-sms-relay) is a working example under the MIT license. It runs on Cloudflare Workers and sends through Vonage. It handles:
>
> * Verification of Ada's webhook signatures.
> * Duplicate webhook deliveries.
> * STOP replies from the caller.
> * The mapping from each caller to their live call.
>
> It also includes a script that creates the SMS channel and the webhook endpoint. Fork it and replace the provider module, or use it as the specification for your own relay.

The flow during a call:

1. The caller talks to the AI Agent on the call.
2. The AI Agent sends a text message. Ada publishes it to your relay as a `v1.conversation.message` webhook.
3. Your relay sends the text message to the caller through your provider.
4. The caller replies by text. Your provider delivers the reply to your relay.
5. Your relay posts the reply to Ada with the [Accept the caller's SMS reply](./../create-sms-reply) endpoint.
6. The AI Agent answers by voice on the call.

> **Note**
>
> Ada cannot confirm delivery of a text message on this channel. Your provider holds the delivery record. Ada receives the caller's texts only when your relay posts them. If the caller replies STOP, handle the opt-out in your relay and do not post it as a reply. Carrier registration and compliance for your number are yours.

## Before you start

You need an [API key](/reference/introduction/authentication) and a webhook endpoint. If you have not built a Conversations API integration before, read [Creating a custom channel](/reference/conversations/developer-guides/creating-a-custom-channel) first. It covers API keys, webhook endpoints, and signing secrets.

The **Use your own SMS channel** toggle in the Ada dashboard controls this feature. It is under **Config > CHANNELS > Voice > Configuration**. While the toggle is off, the AI Agent sends its text messages from the Twilio number configured for your AI Agent.

The toggle appears only while **Allow AI Agent to send and receive SMS** is enabled. You can turn it on only after the SMS channel exists. See [Send texts from your own SMS channel](/docs/channels/voice/voice-onboarding#send-texts-from-your-own-sms-channel).

Ada's APIs include rate limits. Handle `HTTP 429 Too Many Requests` responses with retry logic. See [Conversations API rate limits](/reference/conversations/overview#rate-limits).

## Create the SMS channel

Set `"modality": "sms"` on the [Create a new channel](./../create-channel) request:

**`http`**

```http title="http"
POST https://<handle>.ada.support/api/v2/channels
Authorization: Bearer <your-api-key>
Content-Type: application/json

{
  "name": "My SMS Provider",
  "description": "Relays the AI Agent's voice text messages through our own carrier",
  "modality": "sms"
}
```

> **Warning**
>
> Create this channel once. You cannot delete a channel or change its modality. If a second SMS channel exists, Ada keeps using the first.

## Subscribe your endpoint

Subscribe an endpoint to `v1.conversation.message` before you turn on **Use your own SMS channel**. Scope that endpoint to the new channel ID with [channel filtering](/reference/webhooks/overview#channel-filtering). Ada tags every event from this channel with the channel ID.

If the toggle is on and no enabled endpoint subscribes to `v1.conversation.message`, Ada drops the text message. Ada does not send it from the Twilio number instead.

Ada checks only that an enabled endpoint subscribes to the event type. It does not read the endpoint's **Channels** field. If every subscribed endpoint is scoped to other channel IDs, Ada still publishes the event, and channel filtering delivers it to no endpoint. The caller does not receive the text message. If you set the **Channels** field, include the SMS channel ID.

## What your relay receives

Ada sends one `v1.conversation.message` webhook for each text message. `data.channel` is an object. Its `id` is the ID that Create a new channel returned, and its `modality` is `sms`.

A text message with no image arrives with `content.type` set to `text`:

**`json`**

```json title="json"
"content": {
  "type": "text",
  "body": "Your order ships today. Track it at https://example.com/track"
}
```

A text message with an image arrives with `content.type` set to `link`. The `url` field holds the image. The `link_text` field holds the caption:

**`json`**

```json title="json"
"content": {
  "type": "link",
  "url": "https://example.com/media/receipt.png",
  "link_text": "Here is your receipt."
}
```

Map `url` to your provider's media parameter. Map `link_text` to your provider's message body.

Ada publishes each text message as one body. It does not split a long text message into SMS segments. Your relay handles segmentation for a text message longer than one SMS.

An endpoint scoped to your SMS channel receives the AI Agent's text messages only. It does not receive the caller's spoken turns or any other event from the call. When the call ends, the `400` response to a reply is your relay's only signal.

## Resolve the recipient

The payload carries no phone number. Read the caller's number from the end user:

1. Take `data.end_user_id` from the webhook payload.
2. Call [Get an end user](./../../end-users/get-end-user-by-id).
3. Read `profile.metadata.phone_number`.

Send the text message to that number. If the end user has no `profile.metadata.phone_number`, Ada drops the text message. Ada does not send it from the Twilio number instead.

This channel can send text messages to the caller only. If the AI Agent names another number, Ada drops the text message. Ada does not redirect it to the caller.

## Ada cannot confirm delivery

Your `2xx` response to the webhook is the last thing Ada observes. Ada has no endpoint for a delivery receipt.

* When Ada sends a text message from the Twilio number, Ada receives a delivery status. With this channel, Ada receives none.
* Report delivery and failure in your own relay. Keep `data.message_id` to match each text message to your provider's own record.

## Accept the caller's reply

The caller can reply to a text message during the call. Your provider delivers the reply to your relay. Your relay then posts the reply to the [Accept the caller's SMS reply](./../create-sms-reply) endpoint. The reply becomes the caller's next turn. The AI Agent answers by voice on the live call.

Post each reply as soon as your provider delivers it. The caller is on the live call while your relay works. If the caller stays silent, the AI Agent ends the call after the [caller silence limit](/docs/channels/voice/voice-onboarding#UUID-64669525-022b-0e3b-1569-53881337d173_section-idm43365062568912). That limit is your relay's time budget.

If the caller replies STOP, handle the opt-out in your relay. Do not post STOP as a reply, or the AI Agent treats it as the caller's answer.

Ada accepts a reply only when all of these conditions are true:

* The conversation is a voice call.
* **Use your own SMS channel** is turned on for your AI Agent.
* Your AI Agent has an SMS channel.
* The AI Agent has already sent a text message on this conversation over that channel.
* The call is still live.
* `author.id` is the caller's end user ID.

### What to include in the request

The request body has the same shape as [Create a new message](./../create-message). Read the IDs from the `v1.conversation.message` webhook that carried the AI Agent's text message:

* Path `conversation_id`: Read it from `data.conversation_id`.
* `author.role`: Set it to `end_user`.
* `author.id`: The caller's end user ID. Read it from `data.end_user_id`.
* `content.type`: Set it to `text`.
* `content.body`: The text the caller sent.

Replace `<handle>`, `<conversation_id>`, `<your-api-key>`, and `<end_user_id>` with your actual values.

**`http`**

```http title="http"
POST https://<handle>.ada.support/api/v2/conversations/<conversation_id>/sms/
Authorization: Bearer <your-api-key>
Content-Type: application/json

{
  "author": {
    "role": "end_user",
    "id": "<end_user_id>"
  },
  "content": {
    "type": "text",
    "body": "ABC123"
  }
}
```

### What Ada returns

When Ada accepts the reply, it returns `201` with the message. The `content.body` in the response is the text exactly as your relay sent it.

**`json`**

```json title="json"
{
  "id": "6789abcd1234ef567890",
  "conversation_id": "5df263b7db5a7e6ea03fae9b",
  ...
}
```

> **Example abbreviated for clarity. See the full response [here](./../create-sms-reply#response).**

Ada does not publish the reply as a `v1.conversation.message` event. Your relay already has the text.

Ada stores the reply on the conversation. [Get conversation messages](./../fetch-conversation-messages-by-id) returns it as an `end_user` text message. When the AI Agent answered the reply itself, the stored `content.body` starts with `sms:`. When a Process captured the reply as an input, the body is the bare text. The endpoint does not return the AI Agent's own text messages. If you need a text message later, keep the webhook payload.

Ada does not detect a repeated reply. If your relay posts the same reply twice, the AI Agent answers twice. Retry a reply only after a `429` or a `503` response. When Ada returns either response, it does not store the reply. If the request times out with no response, do not retry. Ada may have accepted the reply.

### When Ada refuses a reply

Ada does not store a refused reply, and the AI Agent does not answer it. Ada returns the standard error body:

**`json`**

```json title="json"
{
  "errors": [
    {
      "type": "bad_request",
      "message": "The call has ended, so this conversation no longer accepts SMS replies.",
      "details": null
    }
  ]
}
```

**Fix the request.** Ada refused the request itself. Correct it, then post the reply again.

| Status | `message`                                                                 | What to do                                                                                |
| ------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `400`  | `Only end user messages are accepted as SMS replies`                      | Set `author.role` to `end_user`.                                                          |
| `400`  | `Only text messages are accepted as SMS replies`                          | Set `content.type` to `text`.                                                             |
| `400`  | `content.body must not be empty`                                          | Send the text the caller wrote, not an empty body.                                        |
| `400`  | `invalid message data`, type `validation_error`                           | Keep the text to 10,000 characters or fewer.                                              |
| `400`  | `Request body validation error`, type `validation_error`                  | Fix the field that `details` names. See [Errors](/reference/introduction/errors).         |
| `400`  | `Invalid path parameter: conversation_id`, type `validation_error`        | Send the conversation ID from `data.conversation_id`.                                     |
| `400`  | `author.id is required for end user messages` or `author.id is invalid`   | Send the end user ID from `data.end_user_id`.                                             |
| `400`  | `author.id is not the caller on this conversation`                        | Send the end user ID from `data.end_user_id`.                                             |
| `404`  | `Conversation with id <conversation_id> not found`                        | Check the conversation ID.                                                                |
| `404`  | `... with end_user_id <end_user_id> not found`, type `resource_not_found` | Send the end user ID from `data.end_user_id`. Your AI Agent has no end user with this ID. |
| `422`  | `SMS replies can only be added to voice conversations`                    | Check the conversation ID. The conversation is not a voice call.                          |
| `422`  | `SMS replies are not enabled for this AI Agent.`                          | Create an SMS channel.                                                                    |
| `422`  | `This AI Agent is not set to use its own SMS channel.`                    | Turn on **Use your own SMS channel** in **Config > CHANNELS > Voice > Configuration**.    |

**Stop.** The call cannot take this reply. Do not post it again.

| Status | `message`                                                                                 | Why                                                                |
| ------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `400`  | `The call has ended, so this conversation no longer accepts SMS replies.`                 | The call is over.                                                  |
| `400`  | `Message dropped: conversation exceeds message limit`                                     | The conversation has more messages than Ada allows.                |
| `422`  | `The AI Agent has not sent an SMS on this conversation, so there is nothing to reply to.` | The AI Agent has not texted on this call. Do not forward the text. |

**Retry.** Ada did not store the reply. Post it again later.

| Status | `message`                                                    | What to do                                                                                            |
| ------ | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| `429`  | `429 Too Many Requests: <limit>`, type `rate_limit_exceeded` | Wait, then retry. See [Conversations API rate limits](/reference/conversations/overview#rate-limits). |
| `503`  | `The SMS channel could not be resolved. Retry the request.`  | Retry later.                                                                                          |

## Test the relay

Use a test AI Agent before you turn the toggle on for a live one.

1. Create the SMS channel. Subscribe an endpoint to `v1.conversation.message`, scoped to the channel ID.
2. Turn on **Use your own SMS channel** in **Config > CHANNELS > Voice > Configuration**.
3. Call the test AI Agent and trigger a **Speech and SMS** capture. See [Voice call capture options](/docs/channels/voice/voice-configuration/voice-call-capture-options).
4. Confirm that your relay receives the webhook and that the text message arrives on your phone.
5. Reply by text. Confirm that your relay posts the reply and receives `201`, and that the AI Agent answers by voice on the call.

## Important notes

* Ada sends each text message as its own webhook, so two text messages in one turn can arrive out of order. Sort the events by `data.created_at`.
* Ada retries a webhook your relay does not accept, so the same text message can arrive twice. Drop a repeated `data.message_id`.
* The AI Agent still speaks to the caller on the voice channel. This channel carries the text messages only.
* When the AI Agent ends the call, it can send the [CSAT survey link](/docs/channels/voice/voice-configuration/voice-csat-configuration) as a text message. That text message also goes over this channel.
* The **Allow AI Agent to send and receive SMS** toggle controls whether the AI Agent can text at all. While it is disabled, the AI Agent sends no text messages over this channel or from the Twilio number. See [Control whether your AI Agent can send SMS](/docs/channels/voice/voice-onboarding#control-whether-your-ai-agent-can-send-sms).
* To send text messages from the Twilio number again, turn off **Use your own SMS channel**. Ada then refuses SMS replies with `422`. A change to the toggle takes effect within a minute, and it can apply to a call that is in progress.