# Getting Started

This guide walks you through creating an **OTP application**, generating API credentials,
and running your first verification from start to finish. The walkthrough uses the sandbox
environment so you can test the integration before moving it to production.

---

## Choose an environment

Create the OTP application and credentials in the same environment as the Verification API
endpoint you plan to use:

| Environment | User Panel | Verification API base URL | Use |
| --- | --- | --- | --- |
| Sandbox | [DIDWW Sandbox User Panel](https://my-sandbox.didww.com/#/users/sign_in) | `https://verification-sandbox.didww.com` | Integration testing |
| Production | [DIDWW Production User Panel](https://my.didww.com/users/sign_in) | `https://verification.didww.com` | Live verifications |

Important

Credentials are environment-specific. Use credentials from a sandbox OTP application
with the sandbox API, and credentials from a production OTP application with the
production API.

---

## Before you begin

- **An account in the selected environment** is required. For testing, open
  the [DIDWW Sandbox User Panel](https://my-sandbox.didww.com/#/otp_verify). For
  production, sign in to the
  [DIDWW Production User Panel](https://my.didww.com/users/sign_in) or
  [create a DIDWW account](https://my.didww.com/users/sign_up).
- **Access to the corresponding User Panel** is required to create an OTP application and
  copy its credentials.
- **A positive account balance** is required for production. The verification fee is billed
  when a verification reaches `verified`. SMS and call delivery costs are billed
  separately.
- **A phone number you control** is required when testing delivery through the production
  environment.

---

## Step 1: Create an OTP application

An **OTP application** groups the credentials and settings used by your verification
requests. You need one before you can start verifications, because each request is tied to an
OTP application key, secret, callback URL, and minimum authentication mode. An account can
have up to 50 OTP applications.

For this walkthrough, create the application in
the [DIDWW Sandbox User Panel](https://my-sandbox.didww.com/#/otp_verify).

1. Open the User Panel for your selected environment: use
   the [DIDWW Sandbox User Panel](https://my-sandbox.didww.com/#/otp_verify) for testing
   or the [DIDWW Production User Panel](https://my.didww.com/users/sign_in) for live
   verifications.
2. Expand **OTP Verify**.
3. Select **Applications**.
4. Click **Create application**.
5. Enter a name for the application.
6. Optionally, enter a description.
7. Set a **callback URL** if you plan to use `public` authentication, including the iOS
   and Android SDK examples in this guide. The Verification API calls this URL so your
   backend can approve or reject each start request. The callback URL is optional for
   `basic` and `application` authentication. See
   [Callbacks](callbacks.html#otp-verification-callbacks).
8. Select the minimum [authentication mode](authentication.html#otp-verification-authentication) required
   for requests to this OTP application:

   - **Public**: anyone with the credential key can call the OTP endpoint.
   - **Basic**: callers must use HTTP Basic authentication with the credential key as the
     username and the credential secret as the password.
   - **Application**: callers must sign each request with the credential secret.
9. Optionally, adjust the **Verification settings**:

   - **Code length**: the number of digits in the code, from 4 to 8. The default is 6.
   - **Code timeout**: how long the code stays valid, counted from when the verification
     starts. Choose 1, 2, 5, or 10 minutes, or **Custom** to enter 60 to 600 seconds. The
     default is 5 minutes.

   Changes to these settings apply to new verifications only.
10. Click **Create** to save the application.

[![Create OTP application form in the DIDWW Sandbox User Panel](../_images/step1.png)](../_images/step1.png)


Create an OTP application in the DIDWW Sandbox User Panel.

## Step 2: Get your API credentials

Each OTP application has a **key** and a **secret**. The key identifies the OTP application
when you make verification requests. The secret is used for stronger authentication modes,
such as HTTP Basic or signed `application` authentication.

Copy the credentials from the OTP application in your selected environment. This walkthrough
uses credentials created in the DIDWW Sandbox User Panel.

The REST API and Ruby SDK examples use both credentials with `basic` authentication. The
iOS and Android SDK examples use only the key with `public` authentication.

1. In **OTP Verify** > **Applications**, find the application you created.
2. In the **Credentials** column, copy the **key** and **secret** for that application.
   Use the copy icon to copy each value without revealing it on screen.
3. If you need to view the actual value before copying it, click the eye icon.

Warning

Treat the secret like a password. Store it securely and never expose it in client-side
code or public repositories. If it is leaked, use **Regenerate secret** on the
application page in the corresponding DIDWW User Panel: the old secret stops working
immediately and the key does not change.

[![OTP application credentials in the DIDWW Sandbox User Panel](../_images/step2.png)](../_images/step2.png)


Copy the key and secret from the Credentials column.

## Step 3: Start a verification

Call the [start endpoint](api-reference/start-verification.html#otp-verification-api-start) with the destination number
and
delivery method. The examples use the sandbox environment and send an SMS code.

Note

The REST API and Ruby SDK examples use `basic` authentication. The iOS and Android SDK
examples use `public` authentication, which requires the callback URL configured in
Step 1. An OTP application with minimum auth mode `public` accepts both authentication
modes. All examples use credentials created in the DIDWW Sandbox User Panel.

REST API

http

```
POST /api/v1/verifications HTTP/1.1
Host: verification-sandbox.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-sandbox.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
    }
  }
}
```

Note

The `Authorization` header contains the Base64 encoding of `key:secret`. Most
HTTP clients build this header when you supply the key as the username and the
secret as the password.


Ruby SDK

Install the [Ruby SDK](sdks/ruby.html#otp-verification-sdk-ruby), then create a sandbox client
and start the verification:

```
require "didww/otp_verification"

client = DIDWW::OTPVerification::Client.new(
  key:    "your-app-key",
  secret: "your-app-secret",
  env:    :sandbox
)

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
```


Python SDK

Install the [Python SDK](sdks/python.html#otp-verification-sdk-python), then create a sandbox
client and start the verification:

```
from didww_verification import (
    BasicAuth,
    Environment,
    SmsOptions,
    VerificationClient,
)

client = VerificationClient(
    BasicAuth("your-app-key", "your-app-secret"),
    environment=Environment.SANDBOX,
)

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
```

`AsyncVerificationClient` is the same surface with `await`.


Node.js SDK

Install the [Node.js SDK](sdks/node.html#otp-verification-sdk-node), then create a sandbox
client and start the verification:

```
import { VerificationClient, basicAuth } from '@didww/verification-core';

const client = new VerificationClient({
  auth: basicAuth('your-app-key', 'your-app-secret'),
  environment: 'sandbox',
});

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

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


PHP SDK

Install the [PHP SDK](sdks/php.html#otp-verification-sdk-php), then create a sandbox client
and start the verification:

```
use Didww\Verification\Auth\BasicAuth;
use Didww\Verification\ClientOptions;
use Didww\Verification\Environment;
use Didww\Verification\Model\DeliveryMethod;
use Didww\Verification\Request\SmsOptions;
use Didww\Verification\VerificationClient;

$client = new VerificationClient(
    new BasicAuth('your-app-key', 'your-app-secret'),
    new ClientOptions(environment: Environment::Sandbox),
);

$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
```


iOS SDK

Install the [iOS SDK](sdks/ios.html#otp-verification-sdk-ios), then create a sandbox client
with public authentication and start the verification:

```
import DIDWWVerification

let client = VerificationClient(
    environment: .sandbox,
    auth: .public(appKey: "your-app-key")
)

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
```


Android SDK

Install the [Android SDK](sdks/android.html#otp-verification-sdk-android). Inside an
`AndroidViewModel`, create a sandbox client with public authentication and start
collecting the verification states. Calling `start(...)` creates a handle. The
request is sent when `handle.states` is first collected.

```
import androidx.lifecycle.viewModelScope
import com.didww.android.sdk.verification.Auth
import com.didww.android.sdk.verification.DeliveryMethod
import com.didww.android.sdk.verification.Environment
import com.didww.android.sdk.verification.SmsOptions
import com.didww.android.sdk.verification.VerificationState
import com.didww.android.sdk.verification.all.DidwwVerification
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch

val didww = DidwwVerification(
    context = application,
    auth = Auth.Public("your-app-key"),
    environment = Environment.Sandbox,
)

val verificationState = MutableStateFlow<VerificationState>(
    VerificationState.Starting,
)

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

viewModelScope.launch {
    handle.states.collect { verificationState.value = it }
}
```


React Native SDK

Install the [React Native SDK](sdks/react-native.html#otp-verification-sdk-react-native). Build the
client once at module scope, then start the verification from the hook. The hook holds
the state your screen renders from.

```
import { VerificationClient, publicAuth } from '@didww/verification-core';
import { useVerification } from '@didww/verification-react-native';

const client = new VerificationClient({
  auth: publicAuth('your-app-key'),
  environment: 'sandbox',
});

const controller = useVerification({ client });

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

controller.state.kind; // 'starting', then 'awaitingInput'
```


Dart SDK

Install the [Dart SDK](sdks/dart.html#otp-verification-sdk-dart), then create a sandbox client
and start the verification:

```
import 'package:didww_verification/didww_verification.dart';

final client = VerificationClient(
  auth: const PublicAuthorization('your-app-key'),
  environment: VerificationEnvironment.sandbox,
);

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
```

A Flutter screen usually wraps this in `VerificationSession` instead, which exposes
the same flow as a stream of states.

Keep the returned `id`, `verification`, `handle`, or controller, depending on your
integration.
You need it to report the code and read the verification status in the next steps.

## Step 4: Report the code

When the user enters the code they received, submit it to the
[report endpoint](api-reference/report-verification.html#otp-verification-api-report):

REST API

http

```
PATCH /api/v1/verifications/0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21 HTTP/1.1
Host: verification-sandbox.didww.com
Content-Type: application/json
Accept: application/json
Authorization: Basic eW91cl9hcHBfa2V5OnlvdXJfYXBwX3NlY3JldA==

{
  "data": {
    "delivery_method": "sms",
    "code": "123456"
  }
}
```

curl

```
curl -i -X PATCH https://verification-sandbox.didww.com/api/v1/verifications/0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21 -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"data": {"code": "123456", "delivery_method": "sms"}}' --user your_app_key:your_app_secret
```

response

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "id": "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21",
    "destination": "4915112345678",
    "delivery_method": "sms",
    "fee": "0.06",
    "status": "verified",
    "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
    }
  }
}
```


Ruby SDK

```
verification = client.report_verification(
  "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21",
  delivery_method: "sms",
  code:            "123456"
)

verification.status    # => "verified"
verification.verified? # => true
```


Python SDK

```
verification = client.report_verification(
    "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21",
    delivery_method="sms",
    code="123456",
)

verification.status  # "verified"
```

An incorrect code raises `DidwwValidationError` with `code_invalid` (`422`), and
the verification stays `pending` for another attempt. Once three attempts are used, the
next report returns `status` `failed`.


Node.js SDK

```
const result = await client.reportVerification(
  '0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21',
  { deliveryMethod: 'sms', code: '123456' },
);

result.status; // 'verified'
```


PHP SDK

```
$verification = $client->reportVerification(
    '0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21',
    DeliveryMethod::Sms,
    '123456',
);

$verification->status;       // "verified"
$verification->isVerified(); // true
```

An incorrect code throws `ValidationException` with `code_invalid` (`422`), and
the verification stays `pending` for another attempt. Once three attempts are used, the
next report returns `status` `failed`.


iOS SDK

```
let result = try await client.verify(
    verification,
    code: "123456"
)

result.status // .verified
```


Android SDK

Submit the code through the handle created in Step 3. The existing state collector
receives the result.

```
handle.submit("123456")
```

A successful submission emits `VerificationState.Submitting` followed by
`VerificationState.Verified`. If the code is rejected, the flow returns to
`VerificationState.AwaitingInput` with `lastError` set.


React Native SDK

Submit through the controller from Step 3. Nothing throws and nothing returns a
promise: the outcome arrives as a new `state`.

```
controller.submit('123456');

// state.kind moves to 'submitting', then 'verified' or back to
// 'awaitingInput' with the error attached.
```


Dart SDK

```
final finished = await client.reportVerification(
  started.id,
  deliveryMethod: started.deliveryMethod,
  value: const ReportValue.code('123456'),
);

finished.knownStatus; // VerificationStatus.verified
```

With `VerificationSession`, call `session.submit(code)` instead and read the
result from the state stream.

A `status` of `verified` confirms the user controls the number. If the code is incorrect,
the report is rejected and the verification remains available for another attempt until the
attempt limit is reached. See [Errors and status codes](api-reference/errors.html#otp-verification-api-errors) for
the exact limit and other status values.

## Step 5: Check the status (optional)

Read the current status of a verification at any time. The REST API and the server-side
SDKs make a status request. The on-device SDKs that drive a state machine - Android, React
Native, and Dart with `VerificationSession` - read the latest state they already hold
rather than issuing a second request.

REST API

http

```
GET /api/v1/verifications/0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21 HTTP/1.1
Host: verification-sandbox.didww.com
Accept: application/json
Authorization: Basic eW91cl9hcHBfa2V5OnlvdXJfYXBwX3NlY3JldA==
```

curl

```
curl -i -X GET https://verification-sandbox.didww.com/api/v1/verifications/0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21 -H "Accept: application/json" --user your_app_key:your_app_secret
```

response

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "id": "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21",
    "destination": "4915112345678",
    "delivery_method": "sms",
    "fee": "0.06",
    "status": "verified",
    "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
    }
  }
}
```


Ruby SDK

```
verification = client.get_verification("0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21")

verification.status    # => "verified"
verification.verified? # => true
```


Python SDK

```
verification = client.get_verification("0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21")

verification.status       # "verified"
verification.is_finished  # True
```

`is_finished` is the signal to stop polling. It is derived from `status`, so a
status added after the SDK was released is treated as finished rather than polled
forever.


Node.js SDK

```
const current = await client.getVerification(
  '0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21',
);

current.status; // 'verified'
```


PHP SDK

```
$verification = $client->getVerification('0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21');

$verification->status;       // "verified"
$verification->isFinished(); // true
```

`isFinished()` is the signal to stop polling. It is `true` for every status other
than `pending`, so a status added after the SDK was released is treated as finished
rather than polled forever.


iOS SDK

```
let current = try await client.status(verification)

current.status // .verified
```


Android SDK

Do not collect `handle.states` a second time. Read the latest value mirrored by the
collector created in Step 3:

```
when (val current = verificationState.value) {
    is VerificationState.AwaitingInput -> println("pending")
    is VerificationState.Verified -> println("verified")
    is VerificationState.Denied -> println("denied")
    is VerificationState.Failed -> println("failed")
    is VerificationState.SetupError -> println("configuration error")
    VerificationState.Expired -> println("expired")
    else -> println("in progress")
}
```


React Native SDK

The controller from Step 3 already holds the current state. Render from it rather
than issuing a status request:

```
switch (controller.state.kind) {
  case 'awaitingInput':
    return <Text>Waiting for the code</Text>;
  case 'verified':
    return <Text>Verified</Text>;
  case 'denied':
  case 'expired':
  case 'failed':
    return <Text>Not verified</Text>;
  default:
    return <Text>Please wait...</Text>;
}
```


Dart SDK

With the client, read the status directly:

```
final current = await client.getVerification(started.id);

current.knownStatus; // VerificationStatus.verified
current.isFinished;  // true
```

With `VerificationSession`, read `session.state` or listen to `session.states`
instead of making a second request.

## Move to production

After the sandbox flow works as expected, configure the integration separately for
production:

1. Sign in to the
   [DIDWW Production User Panel](https://my.didww.com/users/sign_in).
2. Create a new OTP application in the production account and configure its callback URL
   and minimum authentication mode.
3. Copy the key and secret from the production OTP application. Store them separately from
   the sandbox credentials.
4. Change the API or SDK environment from sandbox to production:

   | Integration | Sandbox | Production |
   | --- | --- | --- |
   | REST API | `https://verification-sandbox.didww.com` | `https://verification.didww.com` |
   | Ruby SDK | `env: :sandbox` | `env: :production` or omit `env` |
   | iOS SDK | `environment: .sandbox` | `environment: .production` |
   | Android SDK | `environment = Environment.Sandbox` | `environment = Environment.Production` |
5. Run a controlled verification using a phone number you can access.

Warning

Production requests can contact real destinations and incur verification and delivery
charges. Confirm that the production OTP application, credentials, callback URL, and API
environment are configured together before sending requests.

## Next steps

- Compare the delivery channels in [Verification Methods](verification-methods/index.html#otp-verification-methods).
- Review the available authentication modes and choose the one that fits your integration in
  [Authentication](authentication.html#otp-verification-authentication).
- Gate verifications in real time with a
  [request callback](callbacks.html#otp-verification-callbacks).
- Review every field and status code in the
  [API Reference](api-reference/index.html#otp-verification-api-reference).

On this page
