> For the complete documentation index, see [llms.txt](https://docs.360dialog.com/partner/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.360dialog.com/partner/resources/pre-verified-phone-numbers.md).

# Pre-verified phone numbers

{% hint style="warning" icon="lightbulb-on" %}
**Why this matters?**

Phone number addition and verification are the steps in Embedded Signup where most drop-offs and errors occur. Pre-verified numbers let you handle both steps in advance, so by the time your client goes through the ES flow the number is already ready to connect — no OTP wait, no failed verification attempts. This significantly improves the ES experience and is expected to increase onboarding success rates.
{% endhint %}

## How pre-verification works

Pre-verification lets you verify phone numbers via API before your clients ever reach Embedded Signup. Instead of requiring clients to add a number and enter an OTP themselves during onboarding, you handle that step in advance — either by maintaining a pool of pre-verified numbers ready to be assigned, or by verifying a specific customer's number right before sending them to the ES flow.

Once a number is verified, you pass its ID to the onboarding flow. The client can then select and connect it without going through phone number verification at all. This works whether you are hosting your own Embedded Signup or using 360dialog's Integrated Onboarding.

### Verification statuses

A number moves through the following statuses during its lifecycle:

| Status         | Meaning                                                              |
| -------------- | -------------------------------------------------------------------- |
| `NOT_VERIFIED` | In the pool, ownership not yet proven.                               |
| `VERIFIED`     | Ownership proven. Ready to be claimed during onboarding.             |
| `EXPIRED`      | The 90-day window closed. Must be re-verified before it can be used. |

{% hint style="info" %}
Numbers can be re-verified after 45 days to maintain continuous verified status. We recommend tracking verification dates and re-verifying before the window closes to avoid gaps.
{% endhint %}

## Verifying numbers

This section covers the API endpoints for managing your pre-verified pool: adding numbers, requesting and verifying OTPs, listing the pool, and deleting numbers.

### Process overview

{% stepper %}
{% step %}

### [**Add the number**](#step-1-add-a-number-to-the-pool)

Register it with Meta and get back a `preverified_phone_number_id`
{% endstep %}

{% step %}

### [**Request an OTP**](#step-2-request-an-otp)

Trigger an SMS or voice code to be sent to the number
{% endstep %}

{% step %}

### [**Verify the OTP**](#step-3-verify-the-otp)

Submit the code to prove ownership. The number becomes `VERIFIED` and a 90-day window opens
{% endstep %}

{% step %}

### [**Pass the ID to onboarding**](#passing-pre-verified-numbers-to-onboarding)

Include the `preverified_phone_number_id` when sending the client to your ES flow or Integrated Onboarding link
{% endstep %}

{% step %}

### **Client completes onboarding**&#x20;

They see the number pre-selected and skip verification entirely
{% endstep %}

{% step %}

### [**Receive confirmation**](#webhook-preverified_number_claimed)

A  `preverified_number_claimed` webhook fires when the number is connected, telling you which channel it became
{% endstep %}
{% endstepper %}

### Base URLs

```
https://hub.360dialog.io/api/v2/partners/{partner_id}/preverified-numbers
```

`{partner_id}` is your 360dialog partner ID (e.g. `examplePA`).

### Authentication

Every request must include your **Partner API key** in the `x-api-key` request header.

```bash
curl -H "x-api-key: <YOUR_PARTNER_API_KEY>" \
     https://hub.360dialog.io/api/v2/partners/examplePA/preverified-numbers
```

{% hint style="warning" %}
Treat the API key as a secret. Never embed it in client-side code, URLs that get logged, or version control. Rotate it if it is exposed.
{% endhint %}

### Error format

Errors share a common envelope:

```json
{
  "meta": {
    "360dialog_trace_id": "7234rgt4ubwfr347REQ",
    "success": false,
    "http_code": 400,
    "developer_message": "Validation error",
    "details": {
      "json": { "phone_number": ["String does not match expected pattern."] }
    }
  }
}
```

| Field                | Notes                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------- |
| `360dialog_trace_id` | Include this when contacting support — it identifies the request.                                 |
| `developer_message`  | Human-readable reason.                                                                            |
| `details`            | Present on validation errors; keyed by location (`json`, `query`, `path`) and then by field name. |

A `401` is returned when the API key is missing or invalid.

### Step 1 — Add a number to the pool

Please use this [endpoint](/partner/partner-api/api-reference/preverified-numbers-management.md#post-api-v2-partners-partner_id-preverified-numbers).

Registers a phone number with Meta and shares it with the partner's Meta business so it can later be claimed by a client.

**Request body**

| Field          | Type   | Required | Notes                                                              |
| -------------- | ------ | -------- | ------------------------------------------------------------------ |
| `phone_number` | string | yes      | Digits only, **no leading `+`**. 6–20 characters, pattern `^\d+$`. |

```json
{
    "phone_number": "15550783881"
}
```

**Response — `201 Created`**

```json
{
  "preverified_phone_number_id": "1234567890123456",
  "phone_number": "15550783881",
  "country_code": "us",
  "partner_id": "examplePA",
  "code_verification_status": "NOT_VERIFIED",
  "created_at": "2026-05-26T12:34:56Z"
}
```

| Field                         | Notes                                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
| `preverified_phone_number_id` | Meta's pre-verified phone number ID. Use it as `{id}` in subsequent calls.                        |
| `country_code`                | ISO 3166-1 alpha-2 (lowercase), derived from the number. May be `null` if it can't be determined. |
| `code_verification_status`    | Starts as `NOT_VERIFIED`.                                                                         |

Adding a number already in your pool returns the existing record (idempotent).

**Error cases**

| Status | When                                                                                                                                               |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `phone_number` fails validation, **or** the partner has no Meta business ID configured (`Partner business id is not configured for this partner`). |
| `404`  | The partner does not exist.                                                                                                                        |
| `409`  | The number is already registered in a **different** pool.                                                                                          |
| `502`  | The request to Meta's API failed.                                                                                                                  |

### Step 2 — Request an OTP

Please use this [endpoint](/partner/partner-api/api-reference/preverified-numbers-management.md#post-api-v2-partners-partner_id-preverified-numbers-preverified_phone_number_id-request-otp).

Triggers a Meta OTP to be sent to the number.

**Request body**

| Field         | Type   | Required | Notes                                     |
| ------------- | ------ | -------- | ----------------------------------------- |
| `code_method` | string | yes      | Delivery method. One of `sms`, `voice`.   |
| `language`    | string | yes      | Locale for the OTP message, e.g. `en_US`. |

```json
{
    "code_method": "sms",
    "language": "en_US"
}
```

**Supported `code_method` values**

| Value   | Delivery                                   |
| ------- | ------------------------------------------ |
| `sms`   | Code sent by SMS text message.             |
| `voice` | Code delivered by an automated voice call. |

`language` is a Meta locale code passed straight through to Meta — for example `en_US`, `en_GB`, `de`, `es`, `fr`, `it`, `pt_BR`, `nl`, `id`. Refer to Meta's list of supported languages for the full set. Choose a locale the recipient can read; `voice` calls read the code aloud in that language.

**Response — `200 OK`**

```json
{
    "status": "success",
    "message": "OTP sent.",
    "data": null
}
```

**Error cases**

| Status | When                                                                               |
| ------ | ---------------------------------------------------------------------------------- |
| `400`  | Invalid `code_method`, or Meta rejected the request (e.g. invalid number for OTP). |
| `404`  | No pre-verified number with this ID in the pool.                                   |

### Step 3 — Verify the OTP

Please use this [endpoint](/partner/partner-api/api-reference/preverified-numbers-management.md#post-api-v2-partners-partner_id-preverified-numbers-preverified_phone_number_id-verify-otp).

Submits the code received on the number. On success the number becomes `VERIFIED` and the 90-day window starts.

**Request body**

| Field  | Type   | Required | Notes                                               |
| ------ | ------ | -------- | --------------------------------------------------- |
| `code` | string | yes      | The OTP code received on the number, e.g. `123456`. |

```json
{
    "code": "123456"
}
```

**Response — `200 OK`**

```json
{
  "preverified_phone_number_id": "1234567890123456",
  "phone_number": "15550783881",
  "country_code": "us",
  "partner_id": "examplePA",
  "code_verification_status": "VERIFIED",
  "created_at": "2026-05-26T12:34:56Z"
}
```

**Error cases**

| Status | When                                                       |
| ------ | ---------------------------------------------------------- |
| `400`  | Meta rejected the code (e.g. `Invalid verification code`). |
| `404`  | No pre-verified number with this ID in the pool.           |

### List numbers

Please use this [endpoint](/partner/partner-api/api-reference/preverified-numbers-management.md#get-api-v2-partners-partner_id-preverified-numbers).

Returns the numbers in the partner's pool.

**Query parameters**

| Param                      | Type   | Filters?    | Notes                                         |
| -------------------------- | ------ | ----------- | --------------------------------------------- |
| `code_verification_status` | string | yes (exact) | One of `NOT_VERIFIED`, `VERIFIED`, `EXPIRED`. |
| `phone_number`             | string | yes (exact) | Digits only, no leading `+`, pattern `^\d+$`. |
| `country_code`             | string | **no**      | Ordering hint only — see below.               |

`country_code` (ISO 3166-1 alpha-2, lowercase) **does not filter** the result. Numbers whose country matches are returned **first**, all others follow. Use it to surface the most relevant numbers (e.g. `de`) at the top without hiding the rest of the pool.

**Response — `200 OK`**

```json
[
  {
    "preverified_phone_number_id": "1234567890123456",
    "phone_number": "4915550783881",
    "country_code": "de",
    "partner_id": "examplePA",
    "code_verification_status": "VERIFIED",
    "created_at": "2026-05-26T12:34:56Z"
  }
]
```

**Error cases**

| Status | When                                                                             |
| ------ | -------------------------------------------------------------------------------- |
| `400`  | An invalid query value, e.g. `code_verification_status` outside the allowed set. |

### Delete a number

Please use this [endpoint](/partner/partner-api/api-reference/preverified-numbers-management.md#delete-api-v2-partners-partner_id-preverified-numbers-preverified_phone_number_id).

Deletes the number from Meta and removes it from the partner's pool.

**Response — `200 OK`**

```json
{
    "status": "success",
    "message": "Pre-verified number deleted.",
    "data": null
}
```

**Error cases**

| Status | When                                             |
| ------ | ------------------------------------------------ |
| `404`  | No pre-verified number with this ID in the pool. |
| `502`  | The request to Meta's API failed.                |

## Passing pre-verified numbers to onboarding

Once a number is `VERIFIED`, you need to make it available to your clients during their Embedded Signup session. How you do this depends on how you are delivering the onboarding flow.

### Self-hosted Embedded Signup

If you host the ES flow yourself, you can surface pre-verified numbers in the signup form using [pre-filled form data](https://developers.facebook.com/documentation/business-messaging/whatsapp/embedded-signup/pre-filled-data). Add a `preVerifiedPhone` object with an `ids` array to the `setup` object and assign the `preverified_phone_number_id` values of the numbers you want to make available:

```javascript
{
  scope: "<SCOPE>",
  extras: {
    feature: "<FEATURE>",
    setup: {
      preVerifiedPhone: {
        ids: ["<PREVERIFIED_PHONE_NUMBER_ID>"]
      }
    }
  }
}
```

**Full example**

```javascript
{
  scope: "business_management,whatsapp_business_management",
  extras: {
    feature: "whatsapp_embedded_signup",
    version: 2,
    setup: {
      business: {
        name: "Acme Inc.",
        email: "johndoe@acme.com",
        phone: { code: 1, number: "6505551234" },
        website: "https://www.acme.com",
        address: {
          streetAddress1: "1 Acme Way",
          city: "Acme Town",
          state: "CA",
          zipPostal: "94000",
          country: "US"
        },
        timezone: "UTC-08:00"
      },
      phone: {
        displayName: "Acme Inc.",
        category: "ENTERTAIN",
        description: "Gears and widgets"
      },
      preVerifiedPhone: {
        ids: ["106540352242922", "105954558954427"]
      }
    }
  }
}
```

{% hint style="warning" %}
If a `VERIFIED` number is not claimed within 90 days, its status becomes `EXPIRED` and the client will be required to verify the number themselves during signup. Track verification dates and re-verify numbers before the window closes to avoid this.
{% endhint %}

**Completing the connection**

After the client finishes the ES session, Meta returns a phone number ID. You must pass this to 360dialog to complete the channel setup:

```http
POST /api/v2/account_sharing/{partner_id}/numbers
```

Pass the Meta Phone Number ID received from the ES session in this request. [See the self-hosted Embedded Signup documentation for the full details on this step](/partner/onboarding/partner-hosted-embedded-signup.md).

### 360dialog Integrated Onboarding (direct link or Connect Button)

Pass the `preverified_phone_number_id` directly into your onboarding flow so the number is pre-selected when the client goes through the 360dialog-hosted signup.

#### **Connect Button**

Install the latest version of the Connect Button package:

```bash
npm i 360dialog-connect-button
```

Pass the ID via the `preverified_phone_number_id` query parameter:

```jsx
import { ConnectButton } from '360dialog-connect-button';

const App = () => {
  const handleCallback = callbackObject => {
    console.log('client ID: ' + callbackObject.client);
    console.log('channel IDs: ' + callbackObject.channels);
  };

  return (
    <ConnectButton
      partnerId={'your-partner-id'}
      callback={handleCallback}
      queryParameters={{
        preverified_phone_number_id: 'xxxxxxxxxxxxxxxx',
      }}
    />
  );
};
```

#### **Direct Link**

If you're using a direct link instead of the Connect Button, append the query parameter to the onboarding URL  `...?preverified_phone_number_id=<preverified_phone_number_id>`

```
https://app.360dialog.com/onboarding/<partner_id>?preverified_phone_number_id=<preverified_phone_number_id>
```

## Webhook: `preverified_number_claimed`

When a client claims one of your pre-verified numbers during Embedded Signup, the number is removed from the pool and a channel is created for it. At that moment 360dialog sends a **`preverified_number_claimed`** event to your partner webhook so you can reconcile the claimed number with the channel it became.

### When it fires

* The number was in **your partner pool** (not the 360dialog-owned pool), and
* a **new channel** was created for it during onboarding.

It fires **once**, on the onboarding that creates the channel — not on later re-syncs of an existing channel. Delivery only happens if you have a **webhook URL configured** for the partner.

### Delivery

* **Method:** `POST` to your configured partner webhook URL.
* **Headers:** `Content-Type: application/json`.
* **Expected response:** `2xx`. Delivery is a **single attempt** — not retried on failure, so your endpoint should acknowledge quickly and process asynchronously.

### Payload

| Field                              | Notes                                                                     |
| ---------------------------------- | ------------------------------------------------------------------------- |
| `id`                               | Unique event/delivery ID.                                                 |
| `event`                            | Always `preverified_number_claimed`.                                      |
| `data.preverified_phone_number_id` | Matches the ID returned when you added the number to the pool.            |
| `data.id`                          | The newly created **channel** ID.                                         |
| `data.setup_info.phone_number`     | The claimed phone number.                                                 |
| `data.waba_account`                | The WABA the number was connected to (`external_id` is the Meta WABA ID). |
| `data.client`                      | The client that claimed the number.                                       |

```json
{
  "id": "DM0",
  "event": "preverified_number_claimed",
  "data": {
    "id": "CH0",
    "account_mode": "",
    "status": "created",
    "billing_started_at": null,
    "cancelled_at": null,
    "terminated_at": null,
    "client_id": "CL0",
    "current_limit": null,
    "current_quality_rating": null,
    "has_inbox": false,
    "is_oba": false,
    "is_migrated": false,
    "is_on_biz_app": false,
    "version": 1,
    "hub_status": "live",
    "settings": null,
    "created_at": "2025-09-16T14:43:00Z",
    "setup_info": { "phone_number": "49100100100", "phone_name": "display name" },
    "client": {
      "id": "CL0",
      "name": "client name",
      "partner_payload": null,
      "contact_info": { "email": "client@email.com", "language": "DE" }
    },
    "waba_account": {
      "id": "WA0",
      "name": "Test WABA Name",
      "external_id": "E1",
      "fb_business_id": null,
      "fb_account_status": "unknown",
      "on_behalf_of_business_info": null,
      "namespace": null,
      "settings": {}
    },
    "integration": {
      "enabled": true,
      "state": "running",
      "app_id": "100",
      "hosting_platform_type": "meta_cloud_api"
    },
    "preverified_phone_number_id": "1234567890123456"
  }
}
```

Use `data.preverified_phone_number_id` to match the event back to the number you added to the pool, and `data.id` / `data.waba_account` to know which channel and WABA it became.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.360dialog.com/partner/resources/pre-verified-phone-numbers.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
