Send OTP Codes with an SMS API
Learn how to deliver one-time passcodes with Senddio’s SMS API while keeping code generation, expiry, validation, rate limits, and abuse controls in your application.
Use your backend to generate, expire, and verify one-time codes; use Senddio to deliver the SMS. That boundary matters: a messaging API can transport a code, but it does not prove the person controls the destination or make an authentication decision for your product.
This guide covers a server-side pattern for a login or account-recovery flow. It assumes you have a verified sender configured in Senddio and have reviewed the SMS channel requirements and API reference.
Separate verification from delivery
Treat the OTP as an authentication secret. Your application should create a cryptographically unpredictable value, associate it with a user and a narrowly scoped action, set an expiry, and ensure it can be accepted only once. Store a keyed digest or otherwise protect the code at rest, and never write the code to application logs, analytics, or support tooling.
Senddio receives a message request and queues it for the selected channel. It does not generate the OTP, bind it to a login attempt, validate a submitted code, or decide whether the user is authenticated. Keep those rules inside the system that owns the account.
A safer request flow
- The client asks your backend to start a specific action, such as sign-in.
- Your backend checks account state, destination ownership policy, request frequency, and risk signals before issuing a code.
- The backend generates a fresh code, stores only protected verification state with an expiry and attempt counter, and creates an internal challenge ID.
- The backend sends the SMS using its server-side Senddio credential. Return a generic response to the client so account existence is not revealed.
- The client submits the code and challenge context. Your backend checks expiry, scope, attempt limits, and one-time use, then consumes the challenge atomically.
Use a durable store and an atomic consume operation so two concurrent requests cannot redeem the same code. Bind a challenge to the intended account and operation; a code issued for account recovery should not authorize a payment or a new-device enrollment.
Send the SMS from your server
Provision the SMS sender in the Senddio dashboard and keep the API key in your backend secret store. This cURL example shows the message transport request; replace the sample values with a number and a code created by your application.
curl https://api.senddio.com/v1/send \
-H "Authorization: Bearer $SENDDIO_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "sms",
"from": "+14155552671",
"to": "+14155550123",
"body": "Your Example App code is 482913. It expires shortly."
}'Do not send the Senddio credential to browser or mobile clients. Avoid placing real OTP values in source code, test fixtures shared externally, logs, or screenshots. The payload is intentionally minimal; consult the API documentation and SMS channel guide for current requirements.
Add abuse and replay controls
Apply limits at several levels: by account, destination, IP or device signal, and overall system capacity. Add progressive delays or temporary blocks after repeated requests, and limit failed verification attempts per challenge. Make the response and resend experience clear without exposing whether a target account exists.
Ensure resend creates a well-defined new challenge and invalidates or supersedes the previous one according to your policy. Repeated client retries can otherwise produce confusing duplicate messages. Track your own challenge and business-event IDs so your application can recognize retries; do not assume a messaging request is idempotent unless the API explicitly documents that behavior.
Review current authenticator guidance from NIST SP 800-63B and implementation advice from the OWASP Multifactor Authentication Cheat Sheet. Set controls appropriate to your account risk, jurisdiction, and threat model.
Observe delivery status without confusing states
A successful submission returns an accepted/queued result, not proof that the handset received or displayed the SMS. Persist the message ID returned by the API alongside your internal challenge ID. Use the documented message lookup endpoint or delivery webhooks to update operational status, and make webhook processing safe to receive more than once.
Keep authentication state separate from transport state. A delivery event can help you troubleshoot a delayed message, but only your verification endpoint should decide whether a code is valid. Provide a measured resend option and a safe alternative recovery path for users who cannot receive SMS.
Production checklist
- Generate codes with a cryptographically secure random source and scope each code to one action.
- Expire and consume challenges server-side; enforce failed-attempt and resend limits.
- Protect stored code material and exclude secrets from logs and analytics.
- Keep the API key server-side, and restrict access to production credentials.
- Distinguish accepted, delivered, failed, expired, and verified states in monitoring.
- Test delayed delivery, duplicate requests, concurrent verification, and destination changes.
For adjacent implementation details, see the multi-channel notification workflow and the full developer guides index.