# Verification APIs compared for developers

> Every verification API follows the same two-call pattern, but they differ in how they authenticate, fall back between channels, report results and let you test. This guide compares them from the code side and shows how to write an integration you can move to another provider later.

Source: https://getoxtro.com/blog/compare-the-top-5-sms-verification-platforms-for-developers

Last updated: 2026-10-25

## The pattern every provider shares

Whichever provider you choose, the flow on your server looks like this:

1. The user enters a phone number. Your server calls **start verification** with that number and gets back an identifier for the attempt.
2. The provider generates a code and sends it.
3. The user types the code. Your server calls **check verification** with the identifier, or the phone number, and the code.
4. The provider answers whether the code is right, wrong, expired or out of attempts.

Your server never generates or stores the code. It keeps only the identifier from step 1, usually for a few minutes in the user's session.

## How the main APIs differ

|  | Start | Check | Fallback between channels |
| --- | --- | --- | --- |
| Twilio Verify | Create a Verification on a Verify Service | Create a Verification Check | Start a new verification on another channel |
| Vonage Verify v2 | Send a request with an ordered `workflow` of channels | Submit the code for the request ID | Automatic, through the workflow |
| Prelude | `POST /v2/verification` | `POST /v2/verification/check` | Automatic, across its routes |
| Plivo Verify | Create a verification session | Validate the code for the session | Voice after SMS |
| Sinch Verification | Start a verification with a method: SMS, flash call, call or data | Report the code or caller ID | You choose the method per request |
| Oxtro | `POST /v1/otp/send` | `POST /v1/otp/verify` | None: WhatsApp only |

A few details matter when you write the code.

### Twilio Verify

You first create a Verify Service in the console, which holds settings such as code length and the name shown in the message. Every request then names that service. Authentication is HTTP Basic with your account SID and auth token, or an API key. A failed SMS does not switch channels on its own: your code calls start again with `channel` set to `call` or `whatsapp`. See the [Verify API reference](https://www.twilio.com/docs/verify/api).

### Vonage Verify v2

The start request carries a `workflow` array, for example WhatsApp then SMS then voice, and Vonage moves down the list until the user completes one. Verify v2 authenticates with a JWT bearer token, not the API key and secret of the older Verify API. Silent authentication, where the user's device opens a check URL over mobile data, must be the first step of a workflow. See the [Verify getting started guide](https://developer.vonage.com/en/verify/getting-started).

### Prelude

Prelude's v2 API uses a bearer API key and two endpoints. Before sending, it runs a fraud check on each request and may decline to send, so your code needs to handle a blocked outcome as well as success and failure. Prelude publishes a [migration guide from Twilio](https://prelude.so/blog/migrating-from-twilio-to-prelude) that maps one API onto the other. See the [Verify documentation](https://docs.prelude.so/verify).

### Plivo Verify

Plivo sends by SMS with voice as the fallback. Its Fraud Shield blocks suspected SMS pumping and returns error code 452 for blocked messages, which your code should treat as "do not retry". Verify must be enabled on your account first. See the [Verify overview](https://www.plivo.com/docs/verify/overview/).

### Sinch Verification

Sinch is built around its mobile SDKs as well as REST. With a flash call, the Android SDK reads the caller ID of the missed call and reports it, so the user types nothing. Data verification goes through the carrier and must be enabled by Sinch. See the [Verification API reference](https://developers.sinch.com/docs/verification/api-reference/verification).

### Oxtro

Oxtro, our product, sends codes on WhatsApp from a number you link to your workspace, and uses a bearer API key with the `otp:send` and `otp:verify` scopes. A wrong code returns HTTP `400` with `OTP_INVALID`, `OTP_EXPIRED` or `OTP_MAX_ATTEMPTS`, not `200` with `verified: false`, so handle those as "not verified". The send endpoint accepts an `Idempotency-Key` header. See the [OTP API reference](https://getoxtro.com/docs/otp).

## Write the integration so you can switch

Providers change prices, coverage and terms. Keep the provider behind a small interface in your code so that changing it, or using two at once, is a contained change.

```ts
type StartResult = { attemptId: string; channel: string };

type CheckResult =
  | { status: 'approved' }
  | { status: 'wrong_code' | 'expired' | 'too_many_attempts' };

interface Verifier {
  start(phone: string): Promise<StartResult>;
  check(attemptId: string, code: string): Promise<CheckResult>;
}
```

Each provider gets one adapter that maps its responses onto these few outcomes. Your sign-up and login code only ever sees `Verifier`. This also makes a fallback provider simple: if `start` on the first one throws, call the second.

## Things to get right regardless of provider

- **Normalise phone numbers to E.164** (`+213555123456`) before you call any API. Most failed sends are badly formatted numbers.
- **Rate-limit on your side as well.** Cap starts per phone number and per IP address, and put a CAPTCHA or bot check in front of the form. Provider limits protect the provider; yours protect your bill.
- **Make starts idempotent.** A double-clicked button should not send two codes. Use the provider's idempotency key if it has one, or deduplicate on your side.
- **Return the same error for every failure.** Do not tell an attacker whether a number exists in your system.
- **Expire the attempt identifier with the session.** Do not keep it in a URL or local storage.
- **Log outcomes, not codes.** Record which provider, channel, country and result. That is how you spot a delivery problem or a pumping attack early.

## Testing

Use the provider's test credentials or test phone numbers in automated tests, so no real messages are sent and no money is spent. In your own tests, stub the `Verifier` interface. Before launch, test by hand on real phones in each country you serve.

## Which one to pick

If you want the most channels from one vendor, start with Twilio Verify. If you want fallback handled in one request, Vonage. If fraud is your biggest risk, Prelude or Plivo. If you build a native mobile app and want the least typing, Sinch. If your users are on WhatsApp and you want codes to come from your own number, Oxtro. For a less code-focused overview, read [the top 5 SMS verification providers](https://getoxtro.com/blog/top-5-sms-verification-providers-for-secure-user-authentication).