OTP verification
Send a one-time code to a phone number and check it, entirely from your backend. If you use one of our SDKs in your app, the SDK does the sending and checking — your backend only needs Confirm a verification.
How a verification works
- Send —
POST /v1/auth/initiatewith the phone number. You get arequestId. - Verify — the user types the code;
POST /v1/auth/verifywith therequestIdand the code. - Sign in — when
verifiedistrue, log the user in. If the code was checked anywhere other than your backend, confirm it first with/v1/auth/statusor/v1/auth/verify-code.
Verify or Confirm — which one do I call?
/v1/auth/verify checks the code the user typed. /v1/auth/verify-code asks QuickAuth who verified — it never checks a code and never uses up an attempt.
| Verify an OTP | Confirm a verification | |
|---|---|---|
| You send | requestId + the code the user typed | requestId or idToken — no code |
| It | Compares the code, counts the attempt, marks the login verified | Only reads the result; changes nothing |
| You get | verified true/false and an idToken | success and the verified phoneNumber, countryCode, nationalPhoneNumber |
| Use it when | Your backend shows the code screen and checks the code itself | Our SDK checked the code in your app, and your backend needs proof before creating a session |
Backend only (no SDK): initiate → verify → if verified is true, sign the user in. You don’t need Confirm.
With our SDK: the SDK sends and checks the code. Your app passes the requestId to your backend, and your backend calls Confirm with its secret and signs in the phoneNumber QuickAuth returns — never a number your app sends, which could be forged.
Defaults you can change in the dashboard
| Setting | Default | Allowed |
|---|---|---|
| Code length | 6 digits | 4–8 |
| Code expires after | 5 minutes | 30 seconds – 15 minutes |
| Wrong attempts allowed | 3 | 1–10 |
| New codes per number | 10 per hour | Ask us to change it |
| Fallback to second channel | after 60 seconds undelivered | 30 seconds minimum |
Resending a code
Calling Send again for the same number while a code is still valid re-sends the same code with the same requestId and a fresh expiry. Each resend is a new message and is billed. Resends do not count toward the 10-per-hour limit and do not reset the wrong-attempt counter.
Automatic fallback
If you set a fallback channel in the dashboard (for example WhatsApp first, SMS second), QuickAuth sends the same code on the fallback channel once — immediately if the first channel reports a failure, or if it is not delivered within the fallback window. You do not need to do anything; the user can enter the code from either message. Both attempts appear in deliveries.
Keep these calls on your server
Send an OTP
/v1/auth/initiateCreates a verification and sends a one-time code to the user over SMS or WhatsApp. Returns a requestId you keep and use to verify the code.
A 200 means the code was queued. Delivery happens a moment later; if it fails, QuickAuth retries on your fallback channel automatically. Use Get verification status to see what was delivered.
AuthServer keyX-Client-Id + X-Client-Secret
Request body
identitystringrequiredThe user’s phone number (or email whenidentityTypeisEMAIL). Digits only, with country code — for India919876543210. A leading+, spaces and dashes are stripped. A bare 10-digit number is treated as Indian. Indian numbers must be mobiles (start with 6, 7, 8 or 9). Send the number the same way every time: rate limits and resend matching compare the string exactly, so+919876543210and919876543210count as two different users.identityTypeenumrequiredWhat identity is. Upper case.MOBILEEMAILmodeenumrequiredUpper case.OTP— send a numeric code (almost always what you want)OAUTH— send a one-tap sign-in link over WhatsApp or TruecallerchannelenumoptionalWhere to send the code. Leave it out to use the primary channel set in your dashboard — recommended, so you can change channels without a deploy. Required forOAUTHmode (WHATSAPPorTRUECALLER). Upper case; do not send"auto"here.SMSWHATSAPPEMAILRCSTRUECALLERorderIdstringoptionalYour own reference (an order or signup id). You can verify and look up byorderIdinstead ofrequestId.otpLengthintegeroptionaldefault6Number of digits, 4 to 8. Defaults to your dashboard setting.templateIdstringoptionalSMS only. The 19-digit DLT template id to use instead of your default OTP template.headerstringoptionalSMS only. The 6-letter DLT sender id (e.g.ACMEIN) to use instead of your default.wabaIdstringoptionalWhatsApp only. Send from this specific WhatsApp number instead of letting QuickAuth pick a healthy one.deviceTokenstringoptionalThe token returned by a previous successful login on this device. If the device is still trusted, no OTP is sent and the verification is approved at once — see OneTap.deviceInfoobjectoptionalFree-form device details for your audit trail (model, OS, app version). Max 4 KB.
Response · 200
requestIdstringKeep this. You need it to verify the code and to check status.messagestringOTP sent successfully, orAuthenticated via OneTap. No OTP sent.oneTapAppliedbooleanWhentruethe device was trusted: the user is already verified and no code was sent. Skip the OTP screen.deviceTokenstring | nullStore this on the device and send it on the next login to enable OneTap.
Errors
- 400INVALID_REQUESTBad phone number (too short, too long, or not an Indian mobile), or otpLength outside 4–8.
- 400TEMPLATE_NOT_FOUNDNo usable OTP template: no default SMS template set, the templateId is not yours, or your WhatsApp authentication template is not approved.
- 400DLT_SENDER_NOT_FOUNDNo default SMS sender id set, or the header you passed is not registered.
- 400INCOMPLETE_TEMPLATE_CONFIGYour OTP SMS template has a placeholder with no value configured in the dashboard.
- 400INVALID_MODEmode is not OTP or OAUTH.
- 401INVALID_CLIENT_CREDENTIALSX-Client-Id or X-Client-Secret is missing, unknown or wrong.
- 401CLIENT_INACTIVEYour account has been deactivated.
- 402INSUFFICIENT_BALANCEWallet is empty.
- 402INSUFFICIENT_SMS_BALANCENo SMS credits left.
- 402INSUFFICIENT_WHATSAPP_BALANCENo WhatsApp credits left.
- 402BILLING_NOT_CONFIGUREDBilling is not set up on your account yet.
- 403DLT_REGISTRATION_SUSPENDEDYour DLT registration is suspended, so SMS cannot be sent.
- 409PROVIDER_ERRORNo connected WhatsApp/SMS number for this channel, or Meta has disabled it.
- 429RATE_LIMIT_EXCEEDEDMore than 10 new codes for this number in the last hour (limit set per account).
- 500INTERNAL_ERRORUnexpected server error, including a body that is not valid JSON. Retry; if it persists, send us the requestId.
curl -X POST "https://api.quickauth.in/v1/auth/initiate" \
-H "X-Client-Id: YOUR_CLIENT_ID" \
-H "X-Client-Secret: YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"identity": "919876543210",
"identityType": "MOBILE",
"mode": "OTP",
"orderId": "signup-1029"
}'{
"requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
"message": "OTP sent successfully",
"oneTapApplied": false,
"deviceToken": "a41c0f9e2b7d4c6a8e1f3b5d7c9a0e2f4b6d8c1a3e5f7b9d0c2e4a6f8b1d3c5e"
}Verify an OTP
/v1/auth/verifyChecks the code the user typed. Send either the requestId or your orderId.
A wrong code is not an error. It returns 200 with verified: false and how many tries are left. Errors only come back once the code has expired or the user has run out of attempts — then send a new OTP.
AuthServer keyX-Client-Id + X-Client-Secret
Request body
requestIdstringoptionalFrom Send an OTP. Required unless you sendorderId.orderIdstringoptionalYour reference from Send an OTP. Matches the most recent verification with that orderId.otpstringrequiredThe code the user entered, exactly — trim spaces first.deviceTokenstringoptionalThedeviceTokenfrom Send an OTP. When OneTap is on, a successful verify marks this device as trusted for this number.deviceInfoobjectoptionalFree-form device details for your audit trail. Max 4 KB.
Response · 200
requestIdstringThe verification this result is for.verifiedbooleantrueif the code matched (or the verification was already approved).messagestringVerified successfully,Already verified, orInvalid OTP. 2 attempt(s) remaining.idTokenstring | nullA short-lived (5 minute) signed token proving the verification. Pass it to Confirm a verification from your backend.nullwhenverifiedis false.
Errors
- 400INVALID_REQUESTNeither requestId nor orderId was sent.
- 400TRANSACTION_NOT_FOUNDNo verification with that requestId/orderId on your account.
- 400OTP_EXPIREDThe code is older than your expiry setting (default 5 minutes). Send a new OTP.
- 400MAX_ATTEMPTS_EXCEEDEDToo many wrong codes (default 3). Send a new OTP.
- 401INVALID_CLIENT_CREDENTIALSX-Client-Id or X-Client-Secret is missing, unknown or wrong.
- 401CLIENT_INACTIVEYour account has been deactivated.
- 500INTERNAL_ERRORUnexpected server error, including a body that is not valid JSON. Retry; if it persists, send us the requestId.
curl -X POST "https://api.quickauth.in/v1/auth/verify" \
-H "X-Client-Id: YOUR_CLIENT_ID" \
-H "X-Client-Secret: YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
"otp": "482913"
}'{
"requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
"verified": true,
"message": "Verified successfully",
"idToken": "eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJxdWlja2F1dGguaW4i…"
}Get verification status
/v1/auth/statusReturns where a verification stands and every delivery attempt (primary channel and fallback). Call it from your backend before you sign the user in, and check that authStatus is VERIFIED and identity is the number you expect.
AuthServer keyX-Client-Id + X-Client-Secret
Query parameters
requestIdstringoptionalRequired unless you sendorderId. Wins if you send both.orderIdstringoptionalYour reference. Matches the most recent verification with that orderId.
Response · 200
requestIdstringidentitystringThe phone number or email exactly as you sent it.identityTypeenumMOBILEEMAILmodeenumOTPOAUTHauthStatusenumA verification nobody tries again staysPENDINGafter it expires — treat “PENDING and older than your expiry” as expired.PENDING— code sent, not verified yetVERIFIED— the user entered the right codeEXPIRED— a verify was attempted after expiryFAILED— a verify was attempted after too many wrong codesverifiedAtstring | nullISO-8601 time of verification. Null until verified.orderIdstring | nullcodestring | nullOAUTH mode only: the authorisation code. Always null for OTP.deliveriesarrayOne entry per send. Empty when OneTap approved the login without a code.
Errors
- 400INVALID_REQUESTNeither requestId nor orderId was sent.
- 400TRANSACTION_NOT_FOUNDNo verification with that id on your account.
- 401INVALID_CLIENT_CREDENTIALSX-Client-Id or X-Client-Secret is missing, unknown or wrong.
- 401CLIENT_INACTIVEYour account has been deactivated.
curl -X GET "https://api.quickauth.in/v1/auth/status?requestId=3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c" \
-H "X-Client-Id: YOUR_CLIENT_ID" \
-H "X-Client-Secret: YOUR_CLIENT_SECRET"{
"requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
"identity": "919876543210",
"verifiedAt": "2026-10-16T07:31:12.481Z",
"orderId": "signup-1029",
"mode": "OTP",
"identityType": "MOBILE",
"authStatus": "VERIFIED",
"code": null,
"deliveries": [
{
"channel": "WHATSAPP",
"type": "OTP",
"deliveryStatus": "FAILED",
"attemptNumber": 1
},
{
"channel": "SMS",
"type": "OTP",
"deliveryStatus": "DELIVERED",
"attemptNumber": 1
}
]
}Confirm a verification
/v1/auth/verify-codeThe safest way to finish a login that happened in your app or website through the SDK. Your frontend sends the requestId our SDKs give you (or the idToken, if you call the API directly) to your backend; your backend calls this endpoint with its secret and gets back the verified phone number. Never trust a phone number your frontend sends you directly.
AuthServer keyX-Client-Id + X-Client-Secret
Request body
idTokenstringoptionalFrom the verify response. Valid for 5 minutes. Required unless you sendrequestId.requestIdstringoptionalThe verification id (the SDK calls itsessionId). Used when both are sent.
Response · 200
successbooleantrueonly when the verification isVERIFIED. Whenfalse, every field exceptrequestIdis null — do not sign the user in.phoneNumberstring | nullThe verified number as it was submitted (an email for EMAIL verifications).countryCodestring | null"91"for Indian numbers sent with the country code; null otherwise.nationalPhoneNumberstring | nullThe 10-digit Indian number without 91; null for other countries.identityTypeenum | nullMOBILEEMAILauthTimeinteger | nullWhen the user verified, in Unix seconds.requestIdstring
Errors
- 400INVALID_REQUESTNeither idToken nor requestId was sent.
- 401INVALID_REQUESTThe idToken is invalid, expired, or belongs to another account.
- 404INVALID_REQUESTNo verification with that requestId on your account.
- 401INVALID_CLIENT_CREDENTIALSX-Client-Id or X-Client-Secret is missing, unknown or wrong.
- 401CLIENT_INACTIVEYour account has been deactivated.
curl -X POST "https://api.quickauth.in/v1/auth/verify-code" \
-H "X-Client-Id: YOUR_CLIENT_ID" \
-H "X-Client-Secret: YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"idToken": "eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJxdWlja2F1dGguaW4i…"
}'{
"success": true,
"phoneNumber": "919876543210",
"countryCode": "91",
"nationalPhoneNumber": "9876543210",
"identityType": "MOBILE",
"authTime": 1792143072,
"requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c"
}