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 |
|---|---|---|---|
|
|
Yes |
Phone number to verify in E.164 format. The leading |
|
|
Yes |
The method used to deliver the verification challenge. Supported values are |
|
|
No |
Up to 4096 characters of your own data, for example a session or order reference. It
is passed unchanged to your request callback as
|
|
|
No |
Options for the |
|
Array of |
No |
Preferred message-template languages as BCP 47 tags, ordered from most to least
preferred. Tags are matched exactly, so |
|
|
No |
How the message supports automatic code entry on the user’s device. When omitted, the
application default applies: the domain line is added when the OTP application has an
SMS domain set, and nothing is added otherwise. Added text counts toward the message
length. A malformed object, or one sent together with |
|
|
Yes, in |
|
|
|
For |
Android SMS Retriever application hash: exactly 11 characters from |
|
|
No |
Deprecated. Use |
|
|
No |
Name of a custom SMS template group of your account. It overrides the group set on the
OTP application. An unknown name returns |
|
|
No |
Options for the |
|
Array of |
No |
Preferred announcement languages as BCP 47 tags, ordered from most to least
preferred. These are the same tags with the same semantics as |
|
|
No |
Name of a custom voice template group of your account. It overrides the group set on
the OTP application. An unknown name returns |
The following request body uses the optional fields:
{
"data": {
"destination": "+4915112345678",
"delivery_method": "sms",
"custom": "session=7f3c9a",
"sms": {
"languages": ["de-DE", "en-US"],
"autofill": {"type": "app_hash", "value": "FA+9qCX9VSu"},
"template_group": "checkout"
}
}
}
Response#
The endpoint returns JSON responses. When a verification is created, its
verification object 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 |
|---|---|
|
The verification record was created. Inspect |
|
Authentication failed, credentials are missing or invalid, or the authentication mode is below the minimum configured for the OTP application. |
|
The account balance is insufficient to start a verification. |
|
Request validation failed. This includes an invalid destination, unsupported delivery
method, invalid delivery-method options, an unknown template group, a |
|
A limit refused the start: the 30-second destination cooldown, the daily or monthly
volume limit, or a per-second rate limit. |
See Errors and status codes 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 start can also be denied or fail before delivery and still return 201 Created, with
status denied or failed and a matching error_code. Always inspect both
fields.
Start outcomes#
A 201 Created response carries one of the following outcomes:
|
|
Meaning |
|---|---|---|
|
|
The start was approved, and the API is delivering the challenge. |
|
|
Your request callback returned |
|
|
Your request callback timed out or returned an unusable response. |
|
|
The start used |
|
|
The OTP application is paused. Resume it in the application settings. |
|
|
The destination country is on your account’s country blocklist. |
|
|
The destination cannot be reached with the selected delivery method. Try another delivery method. |
|
|
Sandbox only: the expired test number. The verification is created already expired, and nothing is sent. |
For every outcome except pending, nothing is sent, no fee is charged, and the start does
not count toward the volume limit or start a cooldown; the sandbox expired outcome alone
starts a cooldown, like a sent code. Except for denied_by_callback and
denied_invalid_callback_response, these outcomes are decided before the request
callback, so your callback is not called. The sandbox denied-by-callback test number does not
call it either.
Delivery-method fields#
The response contains an sms or a callout object, named after delivery_method.
Besides the fields described in the verification object, it contains the following fields:
Field |
Type |
Description |
|---|---|---|
|
|
Present only when an autofill marker was added to the message. |
|
|
Deprecated. Use |
|
|
Name of the custom template group that applied, from the request or the OTP
application, or |
|
|
|
|
|
Why a template group was not used, or |
The template_group, template_group_used, and template_fallback_reason fields are
present in both the sms and the callout object.
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.
Only a start that was approved for delivery, or a sandbox start for the expired
test number, starts the cooldown. A denied start,
and a failed start for an unsupported destination, do not. The check runs before the
request callback, so a rejected start normally does not
call it. See Limits for the other limits that return
429.
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 for credential and header requirements.
The SDK examples assume that the corresponding SDK is installed and initialized. See the Ruby, Python, Node.js, PHP, iOS, Android, React Native, and 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.
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,
"template_group": null,
"template_group_used": false,
"template_fallback_reason": null
}
}
}
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,
"template_group": null,
"template_group_used": false,
"template_fallback_reason": null
}
}
}
Ruby SDK#
The Ruby SDK sends the request when start_verification(...) is called and returns a
verification object containing the initial state.
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"
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.
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"
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.
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'
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.
$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"
$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.
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
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.
val handle = didww.start(
destination = "+4915112345678",
method = DeliveryMethod.SMS,
sms = SmsOptions(languages = listOf("en-US")),
)
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.
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'
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.
controller.start({
destination: '+4915112345678',
deliveryMethod: 'sms',
sms: { languages: ['en-US'] },
});
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 refuses it.