You might not be able to signup with us right now as we are currently experiencing a downtime of 15 mins on our product. Request you to bear with us.

Home
Right Chevron Icon
Blog
Right Chevron IconRight Chevron Icon
OTP Verification API for Developers: Node.js, Python, PHP, Java Code Samples (2026)

OTP Verification API for Developers: Node.js, Python, PHP, Java Code Samples (2026)

Kashika Mishra

9
mins read

OTP API for developers Node Python PHP Java code samples thumbnail for Message Central blog

Key Takeways

  • Every OTP API integration comes down to two REST endpoints: send (returns verification ID) and validate (accepts ID and code, returns verified status).
  • Production-quality integration handles 5 specific error categories: invalid number, code mismatch, expired, rate-limited, fraud-detected.
  • Use webhooks for asynchronous delivery status rather than polling, and always verify the signature header.
  • 12-item production checklist before flipping the feature flag: secrets, libphonenumber, SMS Retriever, WebOTP, country dropdown, resend timer, multi-channel fallback, signed webhooks, alerting, app-layer rate limits, STOP handling, runbook.
  • Pick the language your auth service is written in — REST is REST, SDKs are mostly cosmetic.

If you're a developer evaluating an OTP API for a US-targeted application in 2026, you don't want yet another marketing page, you want code that works. This guide is a hands-on walkthrough of integrating a phone number verification API into a production application: REST endpoint reference, working code samples in Node.js, Python, PHP, and Java, error handling, webhook handling, sandbox testing, and a production checklist before you flip the feature flag.

All examples use VerifyNow's REST endpoints, but the patterns transfer to most modern OTP APIs (Twilio Verify, Vonage Verify, Sinch Verify) with minimal changes. Where US-specific concerns apply (10DLC routing, TCPA opt-in, fraud protection), we'll call them out inline.

The Two Endpoints You Need

Every OTP verification flow comes down to two REST calls, regardless of provider:

  1. POST /verification/send: generates an OTP, picks the optimal channel (SMS, WhatsApp, voice), and sends the code to the user's phone. Returns a verification ID for the next call.
  2. POST /verification/validate: accepts the verification ID and the code the user entered. Returns success/failure plus an error code on failure.

Everything else (channel preferences, retry policies, fraud-protection toggles) is configuration on the send call. Most providers expose 8–12 optional parameters; the defaults are sensible for ~80% of use cases.

Node.js: Send and Verify OTP

// Install: npm install axios
const axios = require('axios');

const API_BASE = 'https://cpaas.messagecentral.com/verification/v3';
const API_KEY  = process.env.MC_API_KEY;

async function sendOtp(phoneNumber, channel = 'SMS') {
 const response = await axios.post(`${API_BASE}/send`, {
   countryCode: '1',
   mobileNumber: phoneNumber,
   flowType: channel,        // 'SMS' | 'WHATSAPP' | 'VOICE'
   otpLength: 6,
 }, {
   headers: { 'authToken': API_KEY }
 });
 return response.data.data.verificationId;
}

async function verifyOtp(verificationId, code) {
 const response = await axios.post(`${API_BASE}/validate`, {
   verificationId,
   code,
 }, {
   headers: { 'authToken': API_KEY }
 });
 return response.data.data.verificationStatus === 'VERIFIED';
}

// Usage in your auth flow
const verificationId = await sendOtp('5551234567');
// ... user enters code ...
const verified = await verifyOtp(verificationId, '482917');

Python: Send and Verify OTP

# Install: pip install requests
import os
import requests

API_BASE = 'https://cpaas.messagecentral.com/verification/v3'
API_KEY  = os.environ['MC_API_KEY']

def send_otp(phone_number, channel='SMS'):
   response = requests.post(
       f'{API_BASE}/send',
       json={
           'countryCode': '1',
           'mobileNumber': phone_number,
           'flowType': channel,
           'otpLength': 6,
       },
       headers={'authToken': API_KEY}
   )
   response.raise_for_status()
   return response.json()['data']['verificationId']

def verify_otp(verification_id, code):
   response = requests.post(
       f'{API_BASE}/validate',
       json={
           'verificationId': verification_id,
           'code': code,
       },
       headers={'authToken': API_KEY}
   )
   response.raise_for_status()
   return response.json()['data']['verificationStatus'] == 'VERIFIED'

PHP: Send and Verify OTP

<?php
// Using cURL — no external dependencies needed.

define('API_BASE', 'https://cpaas.messagecentral.com/verification/v3');
define('API_KEY', getenv('MC_API_KEY'));

function sendOtp($phoneNumber, $channel = 'SMS') {
   $ch = curl_init(API_BASE . '/send');
   curl_setopt_array($ch, [
       CURLOPT_RETURNTRANSFER => true,
       CURLOPT_POST => true,
       CURLOPT_HTTPHEADER => [
           'authToken: ' . API_KEY,
           'Content-Type: application/json',
       ],
       CURLOPT_POSTFIELDS => json_encode([
           'countryCode'  => '1',
           'mobileNumber' => $phoneNumber,
           'flowType'     => $channel,
           'otpLength'    => 6,
       ]),
   ]);
   $response = json_decode(curl_exec($ch), true);
   curl_close($ch);
   return $response['data']['verificationId'];
}

function verifyOtp($verificationId, $code) {
   $ch = curl_init(API_BASE . '/validate');
   curl_setopt_array($ch, [
       CURLOPT_RETURNTRANSFER => true,
       CURLOPT_POST => true,
       CURLOPT_HTTPHEADER => [
           'authToken: ' . API_KEY,
           'Content-Type: application/json',
       ],
       CURLOPT_POSTFIELDS => json_encode([
           'verificationId' => $verificationId,
           'code'           => $code,
       ]),
   ]);
   $response = json_decode(curl_exec($ch), true);
   curl_close($ch);
   return $response['data']['verificationStatus'] === 'VERIFIED';
}

Java: Send and Verify OTP

// Using java.net.http (JDK 11+).
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

public class VerifyNowClient {
   private static final String API_BASE = "https://cpaas.messagecentral.com/verification/v3";
   private static final String API_KEY  = System.getenv("MC_API_KEY");
   private static final HttpClient client = HttpClient.newHttpClient();
   private static final ObjectMapper mapper = new ObjectMapper();

   public static String sendOtp(String phoneNumber, String channel) throws Exception {
       String body = mapper.writeValueAsString(java.util.Map.of(
           "countryCode", "1",
           "mobileNumber", phoneNumber,
           "flowType", channel,
           "otpLength", 6
       ));
       HttpRequest req = HttpRequest.newBuilder()
           .uri(URI.create(API_BASE + "/send"))
           .header("authToken", API_KEY)
           .header("Content-Type", "application/json")
           .POST(HttpRequest.BodyPublishers.ofString(body))
           .build();
       HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
       return mapper.readTree(resp.body()).get("data").get("verificationId").asText();
   }

   public static boolean verifyOtp(String verificationId, String code) throws Exception {
       String body = mapper.writeValueAsString(java.util.Map.of(
           "verificationId", verificationId,
           "code", code
       ));
       HttpRequest req = HttpRequest.newBuilder()
           .uri(URI.create(API_BASE + "/validate"))
           .header("authToken", API_KEY)
           .header("Content-Type", "application/json")
           .POST(HttpRequest.BodyPublishers.ofString(body))
           .build();
       HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
       return "VERIFIED".equals(mapper.readTree(resp.body())
           .get("data").get("verificationStatus").asText());
   }
}

Error Handling You Actually Need

The errors that matter in production are not the ones in the happy path. Five you must handle:

Invalid phone number format

Returns HTTP 400 with error code INVALID_NUMBER. Use Google's libphonenumber to normalize before calling the API.

Code mismatch

User entered the wrong OTP. Returns HTTP 200 with verificationStatus: "FAILED". Allow up to 3–5 attempts before invalidating the verification ID.

Code expired

User waited too long. Returns verificationStatus: "EXPIRED". Surface a "Resend code" CTA in your UI.

Rate-limited

Same phone number requested too many OTPs. Returns HTTP 429. Don't retry immediately — back off exponentially.

SMS pumping detected

Returns HTTP 403 with error code FRAUD_DETECTED. Don't retry. Flag the request in your fraud-detection pipeline.

Webhooks for Delivery Status

For production deployments, listen for asynchronous delivery callbacks rather than polling. Configure a webhook URL in your provider dashboard, then handle:

// Express.js webhook handler example
app.post('/webhooks/verifynow', (req, res) => {
 const { verificationId, deliveryStatus, channel, latencyMs } = req.body;

 // Persist for analytics
 db.deliveryEvents.insert({
   verificationId,
   deliveryStatus,    // 'DELIVERED' | 'FAILED' | 'PENDING'
   channel,           // 'SMS' | 'WHATSAPP' | 'VOICE'
   latencyMs,
   timestamp: new Date(),
 });

 // Optional: trigger fallback to alternate channel on failure
 if (deliveryStatus === 'FAILED' && channel === 'SMS') {
   sendOtpFallback(verificationId, 'WHATSAPP');
 }

 res.status(200).end();
});

Always verify webhook authenticity using the signature header your provider sends — never trust an unsigned webhook in production.

Sandbox Testing Before Production

Before flipping the feature flag, validate end-to-end on the sandbox:

  • Test phone numbers: most OTP providers in the US offer reserved test numbers that always succeed/fail/timeout deterministically.
  • Network testing: verify your firewall allows outbound HTTPS to the provider's API endpoints (typically port 443).
  • Rate-limit testing: deliberately trigger a per-number rate limit and confirm your error handling surfaces a useful message.
  • Latency testing: measure round-trip time for send and verify under your typical load.
  • Webhook delivery testing: use a service like webhook.site to inspect callbacks before pointing them at production.

VerifyNow's sandbox uses free test credits with no credit card, so you can run the full integration in pre-production without committing to a contract.

Production Checklist Before Launch

Twelve items to verify before flipping the feature flag:

  1. API keys stored in secrets manager (not source control)
  2. libphonenumber installed and used for client-side normalization
  3. SMS Retriever API integrated on Android (skips manual code entry)
  4. WebOTP API integrated on web (autofill from notification)
  5. Country-code dropdown defaults to user's geo-IP
  6. "Resend code" button hidden for first 30 seconds, then revealed
  7. Multi-channel fallback enabled (SMS → WhatsApp → Voice)
  8. Webhook handler authenticates signature header
  9. Failed-delivery alerting wired to Slack/PagerDuty
  10. Per-IP and per-number rate limiting at your application layer (not just the provider's)
  11. STOP-keyword handling tested (TCPA-compliance)
  12. Production runbook for "OTP delivery is broken" scenarios written and stored

FAQs

Which language has the best SDK for OTP APIs?

Most providers offer official SDKs in Node, Python, Java, PHP, Go, and Ruby with feature parity. The differences between SDKs are mostly cosmetic — REST is REST. Pick the language your team writes the auth service in. If your team writes in Rust or .NET, calling REST endpoints directly with the language's HTTP client (as in the Java example above) is straightforward.

How do I handle OTP fallback to a different channel on failure?

Two patterns: (a) configure automatic provider-side fallback by listing channels in priority order on the send call, and the provider tries each in sequence on delivery failure; (b) listen for delivery webhooks and trigger a new send call with a different channel from your application. The first is simpler; the second gives you more control. VerifyNow supports both patterns.

Should I implement the OTP UI on web with WebOTP?

Yes, where browsers support it. Google's WebOTP API auto-fills the code from an SMS notification on Chrome and Edge. The OTP message must be formatted with a special pattern (e.g., "Your code is 123456 #abc.example.com #482917") for browsers to recognize it. Falls back gracefully to manual entry on unsupported browsers.

Get a Sandbox Key in Under a Minute

The fastest way to validate any of the code above is to run it against a real sandbox. VerifyNow for USA gives you free test credits with no credit card, REST endpoints documented end-to-end, and SDKs in 6 languages. Most teams ship their first OTP integration within a few hours of signup.

Frequently Asked Questions

How do I choose the right OTP service provider?

When selecting an OTP SMS service provider, focus on:

  • Delivery reliability and speed
  • Global coverage and local compliance
  • Multi-channel support and fallback
  • Ease of integration
  • Pricing transparency

The right provider should not just send OTPs but ensure they are delivered consistently across regions and networks.

Not all OTP SMS service providers are built the same.

Some optimize for cost, others for flexibility but very few balance delivery reliability, global coverage and ease of use. And that balance is what actually impacts whether your users receive OTPs on time.

If OTP is critical to your product, focus on:

  • reliable delivery (not just sending)
  • multi-channel fallback
  • scalability across regions

Try It for Yourself

Why is multi-channel OTP important?

Relying only on SMS can lead to failed verifications due to:

  • network issues
  • telecom filtering
  • device limitations

Multi-channel OTP systems (SMS + WhatsApp + voice) improve success rates by automatically retrying through alternative channels if one fails.

What is the best OTP SMS service provider in India?

Some of the commonly used OTP SMS service providers in India include MSG91, Exotel and 2Factor.

That said, India has additional challenges like DLT compliance and operator filtering. Platforms that handle these internally while also offering fallback options tend to provide more consistent OTP delivery.

Which is the cheapest OTP service provider?

Providers like Fast2SMS and 2Factor are often considered among the cheapest OTP service providers, especially in India.

However, lower pricing can come with trade-offs such as:

  • lower route quality
  • higher delivery delays
  • limited fallback options

For mission-critical OTP flows, reliability often matters more than just cost.

Which is the best OTP service provider in 2026?

The best OTP service provider depends on your use case.

  • For global scale and flexibility: Twilio, Infobip
  • For cost-effective APIs: Plivo
  • For India-focused SMS OTP: MSG91, Exotel

However, platforms like Message Central stand out by balancing global coverage, multi-channel fallback and ease of deployment, making them suitable for businesses that prioritize delivery reliability.

What is an OTP service provider?

An OTP service provider enables businesses to send temporary verification codes to users via channels like SMS, WhatsApp or voice to authenticate logins, transactions or sign-ups.

Modern OTP SMS service providers go beyond just sending messages, they ensure reliable delivery using optimized routing, retries and sometimes multi-channel fallback.

Ready to Get Started?

Build an effective communication funnel with Message Central.

Weekly Newsletter Right into Your Inbox

Envelope Icon
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
02271264300
phone-callphone-call