OTP verification
Confirm that a user owns a phone number with a one-time code.
How it works
- Your server calls
POST /v1/otp/sendwith the user’s number. Nopexo creates a verification and texts a code. - You store the
requestId(e.g. in the user’s session) and ask the user for the code. - Your server calls
POST /v1/otp/verifywith therequestIdand what the user typed. - On
verified: true, mark the number as verified. Otherwise show the error and let them retry or resend.
Turn OTP on first
OTP must be enabled for the project (Project → Settings → Services). Defaults — 6 digits, 5-minute expiry, 5 attempts, 60 seconds between sends, 3 sends — can be changed in Verify → Settings.
Send a code
curl -X POST "https://api.nopexo.com/v1/otp/send" \
-H "Authorization: Bearer $NOPEXO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"to": "0200000009",
"purpose": "login",
"reference": "user_42"
}'purpose labels the verification (login, signup, payment…) for your analytics; reference is your own ID, returned with the result and in webhooks.length (4–10) and expiresIn (60–1800 seconds) override your settings per request.
Check the code
curl -X POST "https://api.nopexo.com/v1/otp/verify" \
-H "Authorization: Bearer $NOPEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"requestId": "otp_5hj3k8m2n9p4q7r1",
"code": "123456"
}'| Result | HTTP | What to do |
|---|---|---|
verified: true | 200 | Success. The verification is closed; the code can’t be reused. |
OTP_INCORRECT | 422 | Wrong code. Show attemptsRemaining and let them try again. |
OTP_MAX_ATTEMPTS | 429 | Too many wrong codes. Start a new verification. |
OTP_EXPIRED | 410 | Too late. Start a new verification. |
OTP_ALREADY_VERIFIED | 409 | Already used. Don’t verify again. |
OTP_PHONE_BLOCKED | 429 | Too many wrong codes for this number recently; blocked for an hour. |
Only trust the API
Decide on the server, from the verify response. Never compare codes in your app, and never send the code to the browser.
Resend
After resendAvailableAt, and while sendsRemaining is above zero, you can send a new code for the same verification. The previous code stops working. Each send is charged.
curl -X POST "https://api.nopexo.com/v1/otp/resend" \
-H "Authorization: Bearer $NOPEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"requestId": "otp_5hj3k8m2n9p4q7r1"
}'Check status
curl -X GET "https://api.nopexo.com/v1/otp/otp_5hj3k8m2n9p4q7r1" \
-H "Authorization: Bearer $NOPEXO_API_KEY"Security built in
- Codes come from a cryptographically secure generator. Nopexo stores only a keyed hash, never the code, and never returns it outside sandbox.
- Each check uses one attempt atomically, so parallel guesses can’t exceed the limit, and only one request can ever succeed.
- Limits per number (per project and across Nopexo), per project and per key stop SMS pumping and brute force.
- Optional network restrictions and a daily OTP spending cap (Verify → Settings).
- Suspicious activity — limits hit, blocked numbers, spending caps — is listed in Verify → Overview.
Billing
Each code sent (including resends) is one SMS at the OTP price for the destination. Checking codes is free. Sandbox is free.