OTP / Phone Verification Integration

Add SMS one-time-password verification to signups, logins, and payouts. Affclix routes OTPs across multiple SMS gateways with automatic failover, exposes a simple REST API, signs verification webhooks, and ships with built-in rate limiting and brute-force protection.

Overview

The OTP module lets your application generate and verify phone one-time passwords without managing SMS providers, retries, or fraud rules yourself. You configure one or more gateways in the dashboard, then call two endpoints: send and verify.

Multi-Gateway

Route across MSG91, Twilio, Fast2SMS and more, with priority ordering and automatic fallback.

REST API

Bearer-authenticated send / verify / resend / status endpoints with predictable JSON.

Secure by Default

Hashed codes, HMAC-signed webhooks, per-phone rate limits and lockout against guessing.

Before you start

You need at least one SMS gateway account (e.g. an MSG91 auth key or Twilio SID/token) and access to Dashboard → OTP Integration.

Supported Gateways

Each provider is configured independently and can be marked active, primary, or fallback. The required credentials differ per gateway:

GatewayTypeRequired Credentials
msg91OTP APIAuth key + DLT template ID
twilioSMSAccount SID + Auth token + From number
fast2smsSMSAPI key (+ sender ID)
textlocalSMSAPI key (+ sender ID)
kaleyraSMSAPI key + SID + sender ID
exotelSMSAPI key + API secret + SID
gupshupSMSUser ID + password
customRESTBase URL + Bearer API key
India / DLT compliance

For Indian traffic, register your sender ID and template with the DLT registry and set the DLT template ID on MSG91 providers. Keep DND compliance enabled.

How It Works

01

Configure a provider

Add a gateway in the dashboard with its credentials, OTP length, expiry, and priority.

02

Request an OTP

Your backend calls POST /otp/send with the phone number; Affclix generates a code, stores only its SHA-256 hash, and dispatches the SMS.

03

User submits the code

You call POST /otp/verify with the otp_ref and the code the user typed.

04

Verified + webhook

On success Affclix marks the number verified and fires a signed webhook to your configured URL.

Setup

01

Open OTP Integration

Go to Dashboard → OTP Integration → Add Provider. Pick a gateway type and enter its credentials, sender ID, OTP length, expiry, and priority.

02

Test the connection

Use the Test button to send a live OTP to your own phone and confirm the gateway works before going live.

03

Copy your API key

Open OTP Integration → Settings to copy the API key used for the REST API and to set your verified webhook URL.

Add a fallback

Configure a second provider and mark it as fallback. If the primary gateway fails, Affclix automatically retries delivery through it.

Authentication

All API calls are authenticated with your OTP API key as a Bearer token. The base URL is:

Base URL
https://api.your-domain.com/v1/otp
Authorization header
Authorization: Bearer YOUR_OTP_API_KEY
Content-Type: application/json
Keep the key server-side

Never expose the OTP API key in client-side code. Call the API from your backend only. Rotate the key from Settings if it leaks.

Send OTP

Generates a one-time password and sends it to the phone number. The code itself is never returned — only a reference (otp_ref) you use to verify.

POST /v1/otp/send
{
  "phone": "+919876543210",
  "purpose": "registration",
  "affiliate_id": 1024,
  "provider": "auto",
  "expire_in": 300,
  "metadata": { "campaign_id": 55 }
}

Fields: phone (required), purpose (registration | login | reset | payout), affiliate_id, provider (a gateway slug such as msg91, or auto to route automatically), expire_in (seconds, 60–3600), metadata (free-form object).

Numbers are validated & normalized

Send numbers in E.164 (e.g. +919876543210). Affclix validates and normalizes the number, detects its country, and routes to an eligible provider. Invalid, VoIP, or landline numbers (where blocked) return 422. The normalized number is echoed back in the response.

200 Response
{
  "status": 200,
  "otp_ref": "otp_x7Qa19bKpM2c",
  "phone": "+919876543210",
  "provider": "MSG91 Primary",
  "expires_at": "2026-06-15T10:30:00Z",
  "message": "OTP queued for delivery"
}
Asynchronous delivery

Sends are queued so a slow gateway never blocks your request. Store the otp_ref and poll the status endpoint if you need delivery confirmation.

Verify OTP

Checks the code the user entered against the stored hash for the given reference.

POST /v1/otp/verify
{
  "otp_ref": "otp_x7Qa19bKpM2c",
  "code": "482915"
}
200 — Verified
{
  "verified": true,
  "affiliate_id": 1024,
  "otp_ref": "otp_x7Qa19bKpM2c",
  "message": "Phone number verified successfully."
}
422 — Wrong code
{
  "verified": false,
  "attempts_left": 2,
  "message": "Invalid OTP code."
}
Codes are single-use

A verified or expired reference cannot be reused. After too many wrong attempts the reference is rejected and the phone is temporarily locked — request a fresh OTP.

Resend, Status & Invalidate

Resend

Expires the previous code and issues a new one to the same number. Subject to the resend cooldown.

POST /v1/otp/resend
{ "otp_ref": "otp_x7Qa19bKpM2c" }

Status

Returns the current state of a reference (pending, sent, delivered, failed, expired).

GET /v1/otp/status/{ref}
{
  "otp_ref": "otp_x7Qa19bKpM2c",
  "status": "delivered",
  "is_verified": true,
  "provider": "MSG91 Primary",
  "sent_at": "2026-06-15T10:25:11Z",
  "verified_at": "2026-06-15T10:25:48Z"
}

Invalidate

Force-expires an outstanding code, e.g. when a user restarts a flow.

DELETE /v1/otp/{ref}
{ "message": "OTP invalidated.", "otp_ref": "otp_x7Qa19bKpM2c" }

Two read endpoints are also available: GET /v1/otp/logs (paginated history, filter by status/phone) and GET /v1/otp/providers (active gateways with success rates).

Verified Webhook

When a number is verified, Affclix POSTs a signed event to the verified webhook URL configured on the provider. Delivery is queued and retried on failure.

Webhook request
POST https://your-app.com/webhooks/otp
X-Affclix-Event: otp.verified
X-Affclix-Signature: sha256=<hex_hmac>
Content-Type: application/json

{
  "event": "otp.verified",
  "otp_ref": "otp_x7Qa19bKpM2c",
  "phone": "+919876543210",
  "affiliate_id": 1024,
  "purpose": "registration",
  "verified_at": "2026-06-15T10:25:48Z"
}

Verify authenticity by computing an HMAC-SHA256 of the raw request body using your webhook secret (found in OTP Settings) and comparing it to the header:

PHP
$raw      = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $WEBHOOK_SECRET);
$received = $_SERVER['HTTP_X_AFFCLIX_SIGNATURE'] ?? '';

if (! hash_equals($expected, $received)) {
    http_response_code(401);
    exit('Invalid signature');
}
// signature OK — process the verified event
Respond with 2xx

Return a 2xx status to acknowledge. A non-2xx response is retried with backoff, so make your handler idempotent on otp_ref.

Rate Limits & Cooldown

Several protections run automatically to prevent SMS-bombing and code guessing:

ControlDefaultBehaviour
Per-phone daily cap5 / dayFurther sends return 429 until the next day.
Resend cooldown60 sMinimum gap between sends to the same number.
Per-reference attempts3Wrong codes per otp_ref before it is rejected (configurable per provider).
Per-phone lockout10 fails / 15 minToo many wrong codes across references locks the number temporarily.
Code expiry300 sCodes expire after the provider expiry window.

Error Reference

HTTPMeaningResolution
401Invalid or missing API keySend a valid Bearer token.
404otp_ref not foundCheck the reference belongs to your account.
422Validation / verification failedInvalid, VoIP or landline number; or wrong/expired code on verify.
429Rate limit / cooldownRespect retry_after and the daily cap.
503No provider / all gateways failedAdd an active provider or a fallback.

Best Practices

  • Call the API only from your backend — never expose the API key in a browser or app.
  • Always verify the X-Affclix-Signature on incoming webhooks.
  • Send phone numbers in E.164 format (e.g. +919876543210); they are validated and normalized server-side.
  • Configure at least one fallback provider for delivery resilience.
  • Treat otp_ref as opaque and idempotent; never log full OTP codes.
  • Keep DND/DLT compliance enabled for regulated regions.
  • Use the sandbox environment to test flows without sending real SMS.
Rate this article
★★★★★
Thanks for rating!

Was this article helpful?