Send OTP codes
Goal: deliver one-time passwords (OTP) for login/verification flows over SMS, with the code body masked in logs and reports.
Prerequisites:
- An API key with the
omni-apiscope. - An Active SMS Sender ID (or the generic default) — see Register a Sender ID.
How OTP works over SMS
For SMS, you generate and verify the code yourself; the platform delivers it. The flow is:
- Your backend generates a random code and stores it with an expiry (e.g. 5 minutes).
- You send it via
POST /messageswithisOtp: true. - The user types the code into your app; your backend compares it against the stored value and expiry.
isOtp: true masks the message body in the platform’s logs and reports, so the code is not exposed in delivery records.
The platform also offers a managed verification flow — where the platform generates the code and checks it for you via
POST /messages/verificationandPOST /messages/revoke— but that flow is specific to the Telegram Gateway channel (channel: 8), not SMS. It is described at the end of this guide.
Steps
1. Generate the code in your backend
Use a cryptographically secure random generator, e.g. a 6-digit numeric code. Store it server-side together with the recipient’s phone number, a creation timestamp, and a TTL. Never accept a code after its TTL, and limit verification attempts.
2. Send the code
curl -sS -X POST "https://client.routeon.io/api/v1/messages" \
-H "X-API-Key: <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"contact": "905063565285",
"channel": 1,
"senderId": "Routeon",
"isOtp": true,
"payload": { "text": "Your verification code is 483920. It expires in 5 minutes." },
"clientInfo": "otp;userId=42"
}'
Expected response (202 Accepted):
{
"error": false,
"data": {
"channel": 1,
"transactionId": "95e3e0da-9684-4ba4-88a3-e9207ddeca05",
"messageId": "6719b5f4-36af-4fba-b111-49ff07f4f96b"
}
}
Store messageId so you can check delivery if the user reports the code never arrived.
Keep the OTP text short — an all-GSM-7 body under 160 characters is a single segment and a single charge. See Send an SMS for segmentation rules.
3. Verify the code in your backend
When the user submits the code, compare it to the stored value:
- match and not expired → success;
- mismatch → count the attempt, reject after a small number of tries;
- expired → offer to resend (generate a new code; invalidate the old one in your store).
4. Handle "code didn’t arrive"
Before resending, check what happened to the original message (see Track delivery):
curl -sS -H "X-API-Key: <API_KEY>" \
"https://client.routeon.io/api/v1/edrs?messageIds=6719b5f4-36af-4fba-b111-49ff07f4f96b"
With isOtp: true, the stored body is masked in the record — but mdnStatus, isDelivered, and errorMessage still tell you whether delivery succeeded.
Verify
- The recipient receives the SMS with the code within seconds.
GET /edrsfor themessageIdshowsisDelivered: true(with the body masked).- Your backend accepts the correct code and rejects wrong/expired ones.
Managed verification flow (Telegram Gateway only)
If you also deliver OTPs via the Telegram Gateway channel (channel: 8), the platform can generate and verify the code for you:
POST /messageswithchannel: 8andpayload: { "code_length": 5, "ttl": 300 },isOtp: true— the platform generates the code and delivers it via Telegram; you get back amessageId.- When the user types the code, call:
curl -sS -X POST "https://client.routeon.io/api/v1/messages/verification" \
-H "X-API-Key: <API_KEY>" \
-H "Content-Type: application/json" \
-d '{ "messageId": "019372a1-d252-7908-9216-028bd36c7af1", "code": "12345" }'
Response:
{
"success": true,
"message_id": "019372a1-d252-7908-9216-028bd36c7af1",
"delivery_status": "read",
"verification_status": "code_valid",
"error": ""
}
verification_status values include code_valid, code_invalid, expired, and revoked.
- To invalidate an in-flight code early (user cancelled, resend, suspected fraud):
curl -sS -X POST "https://client.routeon.io/api/v1/messages/revoke" \
-H "X-API-Key: <API_KEY>" \
-H "Content-Type: application/json" \
-d '{ "messageId": "019372a1-d252-7908-9216-028bd36c7af1" }'
These two endpoints work only for Telegram Gateway messages — calling them with an SMS messageId returns 404. For SMS, keep code generation and verification in your own backend as described above.
Common failures
| Symptom | Cause | Fix |
|---|---|---|
| Code arrives late or not at all | Delivery issue on the operator side | Check GET /edrs by messageId: UNDELIV = bad/unreachable number, EXPIRED = delivery window ran out, REJECTD = SMSC rejection (sender or destination route) |
401 on OTP sends |
API key missing or without the omni-api scope |
A missing scope is rejected with 401 before any REJECTD delivery status. See Send an SMS |
REJECTD on OTP sends |
Sender ID not Active, or the destination route is closed | This is an SMSC status, not an authorization error. See the checklist in Send an SMS |
| Code visible in delivery reports | isOtp not set on the send |
Always set "isOtp": true on OTP messages |
404 from POST /messages/verification |
The messageId is not a Telegram Gateway message (e.g. it was an SMS) |
Use the managed verification endpoints only for channel: 8; verify SMS codes in your own backend |
| Users retry and receive multiple valid codes | Old codes not invalidated on resend | Invalidate the previous code in your store whenever you issue a new one |