---
"@context": https://schema.org
"@type": TechArticle
"@id": https://www.twilio.com/docs/usage/webhooks/webhook-migration#article
headline: Migrate to the Webhooks configuration API
description: Move webhook authentication and connection behavior out of your webhook URLs and into Webhook Settings, one endpoint at a time and with no downtime.
url: https://www.twilio.com/docs/usage/webhooks/webhook-migration
inLanguage: en
dateModified: 2026-10-14T20:48:46.000Z
author:
  "@type": Organization
  name: Twilio Developer Education Team
publisher:
  "@type": Organization
  name: Twilio
---

# Migrate to the Webhooks configuration API

Webhook authentication and delivery behavior have long lived in the webhook URL itself: credentials in a `user:password@` prefix, delivery options in a `#ct=…` fragment. The [Webhooks configuration API][config] moves authentication and delivery behavior into [Settings][settings] you manage through the API.

This guide is for endpoints already receiving webhooks. It migrates one endpoint at a time, each step is reversible, and traffic keeps flowing throughout. If you're building an integration from scratch, start with the [quickstart][quickstart].

## Before you start

Rules apply to a single Twilio account. If you keep test and production environments in separate accounts, migrate the test account first.

Settings you create always use [Server Name Indication (SNI)][sni]. You can't turn SNI off using the configuration API: SNI lets your endpoint present the certificate matching its hostname, which is what makes certificate validation meaningful on shared infrastructure.

If your webhook URLs set `sni=n`, make your endpoint work with SNI before you complete [Step 4: Shift traffic to your setting](#step-4-shift-traffic-to-your-setting). Until you shift traffic to your Setting, your traffic uses the system Setting which honors `sni=n` as a connection override.

If your Setting also changes how Twilio signs webhooks, your endpoint has to accept that signature before any traffic uses it. To learn more, see [Sign webhooks with shared keys][keys].

## Step 1: Inventory your webhook URLs

Create a list of the domains and paths Twilio calls. [Rules][rules] match URL patterns, so a single prefix can cover many webhooks at once.

Webhook URLs are set in several places:

* Phone numbers and TwiML apps
* Messaging Services
* Studio flows
* `action` and `statusCallback` attributes on TwiML verbs
* `StatusCallback` parameters on API requests

For each URL, note whether it carries credentials in a `user:password@` prefix, and which [connection override][overrides] keys its fragment sets. You'll reproduce these in a Setting.

## Step 2: Bring one URL under a rule

Create a Rule for the URL that points at the [system Setting][system]. The system Setting reproduces legacy delivery, so nothing about the request changes. This step proves the Rule matches your URL before any behavior depends on it.

Fetch the system Setting's `id`:

```bash
curl -X GET 'https://webhooks.twilio.com/v1/Webhooks/Settings?type=SYSTEM' \
     -u {API_KEY_SID}:{API_KEY_SECRET}
```

Then create the Rule:

```bash
curl -X POST https://webhooks.twilio.com/v1/Webhooks/Rules \
     -H 'Content-Type: application/json' \
     -u {API_KEY_SID}:{API_KEY_SECRET} \
     -d '{
           "matchType": "EXACT_MATCH",
           "value": "https://api.example.com/webhooks/messaging",
           "settings": [ { "settingId": "{SYSTEM_SETTING_ID}", "percentage": 100 } ]
         }'
```

Poll the [Operation][operations] for this rule until its status is `COMPLETED`, then confirm your webhooks still arrive as they did before. Each one now carries an `X-Twilio-Webhooks-Settings` header naming your Rule. If the header is absent, the Rule isn't matching the URL you expected. Check it against [how Twilio matches a rule pattern][rules].

## Step 3: Build your setting

Create a Setting that reproduces what the URL does. These are the equivalents:

| Override or credential       | Setting field                       | Notes                                                                                                               |
| ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `user:password@` credentials | `auth`                              | Inline, or an [AuthProfile][auth] when several Settings share one set of credentials.                               |
| `ct`                         | `connection.connectTimeoutMs`       | Required in a Setting, even when you never set `ct`.                                                                |
| `rt`                         | `connection.readTimeoutMs`          | Required in a Setting, even when you never set `rt`.                                                                |
| `tt`                         | `connection.totalTimeoutMs`         | Optional.                                                                                                           |
| `rc`                         | `connection.retryPolicy.retryCount` | See the warning below.                                                                                              |
| `rp`                         | `connection.retryPolicy.retryOn`    | `4xx`, `5xx`, `ct`, `rt`, and `all` become `HTTP_4XX`, `HTTP_5XX`, `CONNECTION_TIMEOUT`, `READ_TIMEOUT`, and `ALL`. |
| `sni`                        | None                                | Settings always use SNI.                                                                                            |
| `e`                          | `edgeZones.zones`                   | A zone groups edge locations, so delivery survives one of them failing.                                             |

> \[!WARNING]
>
> A URL with no `rc` retries once on connect timeout. A Setting that omits `retryPolicy` doesn't retry at all. To keep retrying, set `retryPolicy` explicitly.

Edge zones are a superset of edge locations. Where `e=dublin` pins delivery to one location, the zone containing Dublin can deliver from another location in that zone when Dublin is unavailable. To learn more, see [Choose webhook edge zones][zones].

[Test the Setting][testing] against your endpoint before any traffic reaches it.

## Step 4: Shift traffic to your setting

`PATCH` the Rule so it splits traffic between the system Setting and yours. Start with a small subset of traffic.

```bash
curl -X PATCH https://webhooks.twilio.com/v1/Webhooks/Rules/{RULE_ID} \
     -H 'Content-Type: application/json' \
     -u {API_KEY_SID}:{API_KEY_SECRET} \
     -d '{
           "settings": [
             { "settingId": "{SYSTEM_SETTING_ID}", "percentage": 90 },
             { "settingId": "{YOUR_SETTING_ID}", "percentage": 10 }
           ]
         }'
```

Wait for the Operation to reach the `COMPLETED` status. The `setting=` value in the `X-Twilio-Webhooks-Settings` header shows which Setting handled each request, so you can watch the split. Once your endpoint is handling your Setting correctly, raise the share of traffic you're routing to your Setting. Once your Setting takes all the traffic, `PATCH` the Rule to a single-entry array. For the full pattern, see [Shift traffic between settings][rules-traffic].

## Step 5: Remove credentials and overrides from the URL

Once your Setting covers the URL, remove the credentials and the fragment it carries, as Twilio no longer requires them.

```bash
https://user:password@api.example.com/webhooks/messaging#ct=2000
```

becomes:

```bash
https://api.example.com/webhooks/messaging
```

Until you do this, the credentials stay in your configuration, your logs, and your source control.

Repeat from step 1 with your next URL.

## Roll back

At any point before step 5, `PATCH` the Rule back to the system Setting at 100%. Legacy delivery resumes once the Operation completes.

```bash
curl -X PATCH https://webhooks.twilio.com/v1/Webhooks/Rules/{RULE_ID} \
     -H 'Content-Type: application/json' \
     -u {API_KEY_SID}:{API_KEY_SECRET} \
     -d '{ "settings": [ { "settingId": "{SYSTEM_SETTING_ID}", "percentage": 100 } ] }'
```

Deleting the Rule works too. Its webhooks go to the next Rule that matches them, or to legacy delivery if no Rule does.

## What's next?

* [Configure webhook settings][settings], for everything a Setting can hold
* [Configure webhook rules][rules], for match types and evaluation order
* [Configuration API FAQs][faq]

[auth]: /docs/usage/webhooks/webhook-auth-profiles

[config]: /docs/usage/webhooks/webhook-configuration

[faq]: /docs/usage/webhooks/webhook-configuration-faq

[keys]: /docs/usage/webhooks/webhook-shared-keys

[operations]: /docs/usage/webhooks/webhook-operations

[overrides]: /docs/usage/webhooks/webhooks-connection-overrides

[quickstart]: /docs/usage/webhooks/webhook-configuration#quickstart

[rules]: /docs/usage/webhooks/webhook-rules

[rules-traffic]: /docs/usage/webhooks/webhook-rules#shift-traffic-between-settings

[settings]: /docs/usage/webhooks/webhook-settings

[sni]: https://en.wikipedia.org/wiki/Server_Name_Indication

[system]: /docs/usage/webhooks/webhook-settings#the-system-setting

[testing]: /docs/usage/webhooks/webhook-testing

[zones]: /docs/usage/webhooks/webhook-edge-zones
