# Start a verification

Create a verification for a destination phone number and select how the verification
challenge is delivered. The response contains the verification identifier, initial status,
expiration time, and delivery-method details when applicable.

For an approved request, the Verification API attempts to deliver the challenge by SMS or
phone call.

## Request

HTTP method: `POST`

Path: `/api/v1/verifications`

### Request body

The request body contains a top-level `data` object. The following table lists the fields
accepted inside `data` and identifies which fields are required:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `destination` | `string` | Yes | Phone number to verify in E.164 format. The leading `+` is optional. |
| `delivery_method` | `string` | Yes | The method used to deliver the verification challenge. Supported values are `sms` and `callout`. |
| `sms` | `object` | No | Options for the `sms` delivery method. Send this object only when `delivery_method` is `sms`. The API ignores it when another delivery method is selected. |
| `sms.languages` | Array of `string` | No | Preferred message-template languages as BCP 47 tags, ordered from most to least preferred. Tags are matched exactly, so `pl` does not match `pl-PL`. If none of the requested languages has a template, the API uses `en-US`. Invalid tags return `languages_invalid`. See [Supported languages](../verification-methods/sms.html#otp-verification-sms-languages). |
| `sms.app_hash` | `string` | No | Android SMS Retriever application hash. It must contain exactly 11 characters from `A-Z`, `a-z`, `0-9`, `+`, and `/`. When supplied, the SMS message starts with `<#>` and ends with the application hash so a compatible Android application can capture the code automatically. Omit this field on other platforms. |
| `callout` | `object` | No | Options for the `callout` delivery method. Send this object only when `delivery_method` is `callout`. The API ignores it when another delivery method is selected. |
| `callout.languages` | Array of `string` | No | Preferred announcement languages as BCP 47 tags, ordered from most to least preferred. These are the same tags with the same semantics as `sms.languages`, so one language list works for both delivery methods. Tags are matched exactly, so `pt` does not match `pt-PT`. If none of the requested languages has a recording, the announcement uses `en-US`. Invalid tags return `languages_invalid`. See [Supported languages](../verification-methods/phone-call.html#otp-verification-callout-languages). |

## Response

The endpoint returns JSON responses. When a verification is created, its
[verification object](index.html#otp-verification-object-reference) appears under a top-level
`data` key. Errors that prevent a verification from being created appear under a
top-level `errors` array.

The following table lists the HTTP status codes returned by this endpoint:

| Status | Meaning |
| --- | --- |
| `201 Created` | The verification record was created. Inspect `status` and `error_code` to determine whether delivery was approved or denied. |
| `401 Unauthorized` | Authentication failed, credentials are missing or invalid, or the authentication mode is below the minimum configured for the OTP application. |
| `402 Payment Required` | The account balance is insufficient to start a verification. |
| `422 Unprocessable Content` | Request validation failed. This includes an invalid destination, unsupported delivery method, or invalid delivery-method options. |
| `429 Too Many Requests` | A verification for the same destination was started less than 30 seconds ago. The `Retry-After` header gives the number of seconds to wait. |

See [Errors and status codes](errors.html#otp-verification-api-errors) for the error response
structure and available error codes.

Note

A `201 Created` response confirms that the verification record was created. It does not
confirm that the challenge was delivered. An approved start normally has `status`
`pending`, and delivery can still fail later.

A [request callback](../callbacks.html#otp-verification-callbacks) can deny the start before delivery.
A denied start can also return `201 Created` with `status` `denied` and a matching
`error_code`. Always inspect both fields.

### Repeated starts

An OTP application cannot start a new verification for the same destination within 30
seconds of the previous one, whichever delivery method either request uses. Such a request
returns `429 Too Many Requests` with the `destination_in_cooldown` error code and a
`Retry-After` header: the whole number of seconds, at least `1`, until a new start is
accepted. The earlier verification is not changed and stays active.

A start that was denied does not count. The check runs before the
[request callback](../callbacks.html#otp-verification-callbacks), so a rejected start normally does not
call it.

```
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 18

{
  "errors": [
    {
      "code": "destination_in_cooldown",
      "detail": "verification for this destination was requested too recently"
    }
  ]
}
```

Do not retry a rejected start automatically. Let the user request a new code once the
`Retry-After` interval has passed.

## Examples

The REST API examples use HTTP Basic authentication. See
[REST API authentication](../authentication.html#otp-verification-authentication) for credential and header
requirements.

The SDK examples assume that the corresponding SDK is installed and initialized. See the
[Ruby](../sdks/ruby.html#otp-verification-sdk-ruby),
[Python](../sdks/python.html#otp-verification-sdk-python),
[Node.js](../sdks/node.html#otp-verification-sdk-node),
[PHP](../sdks/php.html#otp-verification-sdk-php),
[iOS](../sdks/ios.html#otp-verification-sdk-ios),
[Android](../sdks/android.html#otp-verification-sdk-android),
[React Native](../sdks/react-native.html#otp-verification-sdk-react-native), and
[Dart](../sdks/dart.html#otp-verification-sdk-dart) SDK pages.

The identifiers, expiration times, and fees shown in the examples are illustrative.

### REST API

Send a `POST` request to create a verification. When the record is created, the response
returns a verification object containing its initial state under a top-level `data` key.

SMS

http

```
POST /api/v1/verifications HTTP/1.1
Host: verification.didww.com
Content-Type: application/json
Accept: application/json
Authorization: Basic eW91cl9hcHBfa2V5OnlvdXJfYXBwX3NlY3JldA==

{
  "data": {
    "destination": "+4915112345678",
    "delivery_method": "sms",
    "sms": {
      "languages": ["en-US"]
    }
  }
}
```

curl

```
curl -i -X POST https://verification.didww.com/api/v1/verifications -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"data": {"delivery_method": "sms", "destination": "+4915112345678", "sms": {"languages": ["en-US"]}}}' --user your_app_key:your_app_secret
```

response

```
HTTP/1.1 201 Created
Content-Type: application/json

{
  "data": {
    "id": "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21",
    "destination": "4915112345678",
    "delivery_method": "sms",
    "fee": "0.06",
    "status": "pending",
    "error_code": null,
    "error_detail": null,
    "expires_at": "2026-07-15T10:05:00.000Z",
    "sms": {
      "template": "Your code is {{CODE}}",
      "language": "en-US",
      "interception_timeout": 300,
      "code_length": 6
    }
  }
}
```


Phone call

http

```
POST /api/v1/verifications HTTP/1.1
Host: verification.didww.com
Content-Type: application/json
Accept: application/json
Authorization: Basic eW91cl9hcHBfa2V5OnlvdXJfYXBwX3NlY3JldA==

{
  "data": {
    "destination": "+4915112345678",
    "delivery_method": "callout",
    "callout": {
      "languages": ["de-DE"]
    }
  }
}
```

curl

```
curl -i -X POST https://verification.didww.com/api/v1/verifications -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"data": {"callout": {"languages": ["de-DE"]}, "delivery_method": "callout", "destination": "+4915112345678"}}' --user your_app_key:your_app_secret
```

response

```
HTTP/1.1 201 Created
Content-Type: application/json

{
  "data": {
    "id": "2b3c4d5e-6f70-4b3c-9d0e-1f2a3b4c5d6e",
    "destination": "4915112345678",
    "delivery_method": "callout",
    "fee": "0.08",
    "status": "pending",
    "error_code": null,
    "error_detail": null,
    "expires_at": "2026-07-15T10:05:00.000Z",
    "callout": {
      "language": "de-DE",
      "code_length": 6
    }
  }
}
```

### Ruby SDK

The Ruby SDK sends the request when `start_verification(...)` is called and returns a
verification object containing the initial state.

SMS

```
verification = client.start_verification(
  destination:     "+4915112345678",
  delivery_method: "sms",
  sms:             {languages: ["en-US"]}
)

verification.id           # => "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21"
verification.status       # => "pending"
verification.pending?     # => true
verification.sms_language # => "en-US"
```


Phone call

```
verification = client.start_verification(
  destination:     "+4915112345678",
  delivery_method: "callout",
  callout:         {languages: ["de-DE"]}
)

verification.id               # => "2b3c4d5e-6f70-4b3c-9d0e-1f2a3b4c5d6e"
verification.status           # => "pending"
verification.callout_language # => "de-DE"
```

### Python SDK

The Python SDK sends the request when `start_verification(...)` is called and returns a
`Verification` containing the initial state.

SMS

```
verification = client.start_verification(
    destination="+4915112345678",
    delivery_method="sms",
    sms=SmsOptions(languages=["en-US"]),
)

verification.id            # "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21"
verification.status        # "pending"
verification.is_finished   # False
verification.sms.language  # "en-US"
```


Phone call

```
verification = client.start_verification(
    destination="+4915112345678",
    delivery_method="callout",
    callout=CalloutOptions(languages=["de-DE"]),
)

verification.id                # "2b3c4d5e-6f70-4b3c-9d0e-1f2a3b4c5d6e"
verification.status            # "pending"
verification.callout.language  # "de-DE"
```

A denied start returns a `Verification` with status `denied` rather than raising.
`AsyncVerificationClient` is the same call with `await`.

### Node.js SDK

The Node.js SDK sends the request when `startVerification(...)` is called and returns the
decoded verification.

SMS

```
const verification = await client.startVerification({
  destination: '+4915112345678',
  deliveryMethod: 'sms',
  sms: { languages: ['en-US'] },
});

verification.id;            // '0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21'
verification.status;        // 'pending'
verification.sms?.language; // 'en-US'
```


Phone call

```
const verification = await client.startVerification({
  destination: '+4915112345678',
  deliveryMethod: 'callout',
  callout: { languages: ['de-DE'] },
});

verification.id;                // '2b3c4d5e-6f70-4b3c-9d0e-1f2a3b4c5d6e'
verification.status;            // 'pending'
verification.callout?.language; // 'de-DE'
```

A start is never retried by the SDK. A repeated start supersedes the live verification and
sends a new code. The superseded verification is never charged; the fee is charged only if
the new verification becomes `verified`. Within 30 seconds of the previous start, a
repeated start is rejected with `429 Too Many Requests` instead.

### PHP SDK

The PHP SDK sends the request when `startVerification(...)` is called and returns a
`Verification` containing the initial state.

SMS

```
$verification = $client->startVerification(
    '+4915112345678',
    DeliveryMethod::Sms,
    sms: new SmsOptions(languages: ['en-US']),
);

$verification->id;             // "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21"
$verification->status;         // "pending"
$verification->isFinished();   // false
$verification->sms?->language; // "en-US"
```


Phone call

```
$verification = $client->startVerification(
    '+4915112345678',
    DeliveryMethod::Callout,
    callout: new CalloutOptions(languages: ['de-DE']),
);

$verification->id;                 // "2b3c4d5e-6f70-4b3c-9d0e-1f2a3b4c5d6e"
$verification->status;             // "pending"
$verification->callout?->language; // "de-DE"
```

A denied start returns a `Verification` with status `denied` rather than throwing.

### iOS SDK

The iOS SDK sends the request when `start(...)` is called and returns a `Verification`
containing the initial state.

SMS

```
let verification = try await client.start(
    destination: "+4915112345678",
    method: .sms,
    sms: .init(languages: ["en-US"])
)

verification.id     // "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21"
verification.status // .pending
```


Phone call

```
let verification = try await client.start(
    destination: "+4915112345678",
    method: .callout,
    callout: .init(languages: ["de-DE"])
)

verification.id     // "2b3c4d5e-6f70-4b3c-9d0e-1f2a3b4c5d6e"
verification.status // .pending
```

Inspect `verification.status` before asking the user for a code. A denied start returns a
`Verification` with status `.denied` instead of throwing an HTTP error.

### Android SDK

The Android SDK returns a `VerificationHandle`. Calling `start(...)` does not send the
request. Collect `handle.states` exactly once to send the request and receive verification
state changes. After `VerificationState.Starting`, an approved start emits
`VerificationState.AwaitingInput`, which corresponds to the API status `pending`.

SMS

```
val handle = didww.start(
    destination = "+4915112345678",
    method = DeliveryMethod.SMS,
    sms = SmsOptions(languages = listOf("en-US")),
)
```


Phone call

```
val handle = didww.start(
    destination = "+4915112345678",
    method = DeliveryMethod.CALLOUT,
    callout = CalloutOptions(languages = listOf("de-DE")),
)
```

Collect the returned handle from a ViewModel-scoped coroutine:

```
viewModelScope.launch {
    handle.states.collect { state ->
        when (state) {
            is VerificationState.AwaitingInput ->
                println("Verification ${state.verificationId} is pending")
            is VerificationState.Denied ->
                println(state.error?.detail ?: "Verification denied")
            is VerificationState.SetupError ->
                println("Configuration error: ${state.code}")
            is VerificationState.Failed ->
                println("Start failed: ${state.reason}")
            else -> Unit
        }
    }
}
```

### Dart SDK

The Dart SDK sends the request when `startVerification(...)` is called and returns a
`Verification` containing the initial state.

SMS

```
final started = await client.startVerification(
  destination: '+4915112345678',
  deliveryMethod: DeliveryMethod.sms,
  sms: const SmsOptions(languages: ['en-US']),
);

started.id;            // '0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21'
started.knownStatus;   // VerificationStatus.pending
started.sms?.language; // 'en-US'
```


Phone call

```
final started = await client.startVerification(
  destination: '+4915112345678',
  deliveryMethod: DeliveryMethod.callout,
  callout: const CalloutOptions(languages: ['de-DE']),
);

started.id;                // '2b3c4d5e-6f70-4b3c-9d0e-1f2a3b4c5d6e'
started.knownStatus;       // VerificationStatus.pending
started.callout?.language; // 'de-DE'
```

A Flutter screen usually calls `session.start(...)` on a `VerificationSession` instead,
which performs the same request and publishes the result as a state.

### React Native SDK

The React Native SDK starts the verification through the `useVerification()` controller.
Nothing throws and nothing returns a promise: the outcome arrives as a new `state`.

SMS

```
controller.start({
  destination: '+4915112345678',
  deliveryMethod: 'sms',
  sms: { languages: ['en-US'] },
});
```


Phone call

```
controller.start({
  destination: '+4915112345678',
  deliveryMethod: 'callout',
  callout: { languages: ['de-DE'] },
});
```

`state.kind` moves to `'starting'` and then to `'awaitingInput'` for an approved start,
or to `'denied'` when a [request callback](../callbacks.html#otp-verification-callbacks) refuses it.

On this page
