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.
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:
| Gateway | Type | Required Credentials |
|---|---|---|
msg91 | OTP API | Auth key + DLT template ID |
twilio | SMS | Account SID + Auth token + From number |
fast2sms | SMS | API key (+ sender ID) |
textlocal | SMS | API key (+ sender ID) |
kaleyra | SMS | API key + SID + sender ID |
exotel | SMS | API key + API secret + SID |
gupshup | SMS | User ID + password |
custom | REST | Base URL + Bearer API key |
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
Configure a provider
Add a gateway in the dashboard with its credentials, OTP length, expiry, and priority.
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.
User submits the code
You call POST /otp/verify with the otp_ref and the code the user typed.
Verified + webhook
On success Affclix marks the number verified and fires a signed webhook to your configured URL.
Setup
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.
Test the connection
Use the Test button to send a live OTP to your own phone and confirm the gateway works before going live.
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.
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:
https://api.your-domain.com/v1/otp Authorization: Bearer YOUR_OTP_API_KEY
Content-Type: application/json 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.
{
"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).
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.
{
"status": 200,
"otp_ref": "otp_x7Qa19bKpM2c",
"phone": "+919876543210",
"provider": "MSG91 Primary",
"expires_at": "2026-06-15T10:30:00Z",
"message": "OTP queued for 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.
{
"otp_ref": "otp_x7Qa19bKpM2c",
"code": "482915"
} {
"verified": true,
"affiliate_id": 1024,
"otp_ref": "otp_x7Qa19bKpM2c",
"message": "Phone number verified successfully."
} {
"verified": false,
"attempts_left": 2,
"message": "Invalid OTP code."
} 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.
{ "otp_ref": "otp_x7Qa19bKpM2c" } Status
Returns the current state of a reference (pending, sent, delivered, failed, expired).
{
"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.
{ "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.
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:
$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 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:
| Control | Default | Behaviour |
|---|---|---|
| Per-phone daily cap | 5 / day | Further sends return 429 until the next day. |
| Resend cooldown | 60 s | Minimum gap between sends to the same number. |
| Per-reference attempts | 3 | Wrong codes per otp_ref before it is rejected (configurable per provider). |
| Per-phone lockout | 10 fails / 15 min | Too many wrong codes across references locks the number temporarily. |
| Code expiry | 300 s | Codes expire after the provider expiry window. |
Error Reference
| HTTP | Meaning | Resolution |
|---|---|---|
401 | Invalid or missing API key | Send a valid Bearer token. |
404 | otp_ref not found | Check the reference belongs to your account. |
422 | Validation / verification failed | Invalid, VoIP or landline number; or wrong/expired code on verify. |
429 | Rate limit / cooldown | Respect retry_after and the daily cap. |
503 | No provider / all gateways failed | Add 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.