Back to blog
Developers16 min read

OTP SMS in Production: Delivery, Security, Cost, and What Breaks at Scale

A full production checklist for SMS one-time passwords in Ghana: Sender IDs, timeouts, retries, webhooks, fraud, and how SplitSMS OTP APIs fit a real login stack.

A demo OTP that arrives on your phone is not a production OTP system. Production means the code arrives in seconds on MTN, Telecel, and AirtelTigo, expires on time, cannot be reused, is rate-limited, and does not bankrupt you when a bot hammers /login.

SplitSMS gives you send/verify style OTP endpoints, sandbox keys, and delivery webhooks so you are not inventing carrier accounts. This article is the operational layer around those APIs — the parts that usually fail after launch.

Use a dedicated OTP Sender ID and template

Do not send “Your code is 123456” from a marketing Sender ID that also blasts sales. Carriers and users treat OTP as transactional. Register a clear brand ID and keep the template stable: {brand}: your code is {code}. Valid 10 min. Do not share.

Changing the wording every week looks like phishing. Ghana users already see a lot of fake bank texts. Consistency plus an approved Sender ID is half of trust.

Generate codes you can defend

Six digits, cryptographically random, hashed at rest if you store them, single use, 5–10 minute TTL. Do not log raw codes in application logs. Do not put the code in a URL query string that will hit analytics.

SplitSMS can deliver the SMS; your app still owns session binding: this code belongs to this user-id or phone, created at this timestamp, with this attempt counter. Verify should increment attempts and lock after a small number of guesses.

Normalize Ghana numbers at the edge

If the UI accepts 0201234567, convert to 233201234567 before you call SplitSMS. If you accept +233, strip formatting. Failed OTP is often “we stored 020… and sent 20…” — a missing digit, not a downed network.

Reject numbers that are too short after normalization. Show the user the destination you will text (masked) so they can correct a typo before you spend a credit.

Rate limit like an attacker is already here

Cap OTP sends per phone per hour and per IP per hour. Bots will use you as a free SMS cannon against random Ghana numbers. That is a cost incident and a carrier reputation incident.

Add a cooldown between resends (45–60 seconds). SplitSMS will send what you ask; it will not know that the same phone requested 200 codes. That is your job. Use CAPTCHA on public signup if abuse appears.

Retries, idempotency, and “I didn’t get it”

The first user complaint is always “SMS didn’t come.” Check: wallet balance, Sender ID status, destination format, and the delivery webhook or dashboard log for that message id. Resend should invalidate or supersede the previous code so two valid codes are not floating.

Do not retry in a tight loop on your server. If the provider returns a 4xx for a bad number, stop. If it is 5xx or timeout, retry with backoff and an idempotency key so you do not double-send on network blips.

Webhooks close the loop

Subscribe to delivery webhooks, verify HMAC signatures, and store status on the OTP record. Your support screen should show “delivered to network at 14:02” instead of “we think we sent it.”

Pending for many minutes on one network may be a routing issue — split a sample across networks before you blame the user. The SplitSMS delivery webhook article covers payload fields and retries.

Cost control for OTP at scale

OTP is usually one segment. Cost is volume × rate. A fintech sending 50,000 OTP a day needs wallet alerts and a daily cap dashboard-side so a bug cannot drain GHS thousands overnight. SplitSMS low-balance emails exist so someone gets pinged before sends start failing.

Sandbox keys exist so CI and staging never hit live credits. Promote to live keys only on the production server. Never ship sk_live_ in a mobile app.

SMS OTP vs WhatsApp vs authenticator apps

Authenticator apps are stronger against SIM-swap. SMS OTP is what Ghana users complete without installing anything. Many products use SMS for signup and offer TOTP later. WhatsApp OTP depends on the user having WhatsApp and data. SMS remains the fallback that works on a feature phone in a village.

SplitSMS is the SMS layer. If you add WhatsApp later, keep SMS as backup for the users who will never scan a QR.

Launch checklist

Approved Sender ID. Sandbox test. Live test on three networks. Rate limits. Expiry and attempt caps. Webhooks verified. Wallet alert. No codes in logs. Masked phone on the verify screen. Support playbook for “I didn’t get the SMS.”

Create an API key with sms.send from the SplitSMS dashboard, read /api-docs, and start with the OTP endpoints rather than a homemade “send random number via bulk SMS” job. The bulk endpoint is the wrong tool for login codes.

SIM-swap and social engineering

SMS OTP is phishable. Attackers call users and ask them to read the code. Your UI should say SplitSMS / your brand will never call to ask for this code. High-value actions (password change, payout account) can require a second factor or a delay.

You cannot fix SIM-swap with a nicer template. You can reduce damage with alerts: “A login code was sent. If this was not you, reset your password.” SplitSMS can deliver that SMS; your app decides when to send it.

International numbers and roaming

If your product has Ghana plus Nigeria or diaspora users, do not assume one Sender ID and one rate. Check SplitSMS country pricing, test a real roaming handset if you claim “works abroad,” and fail closed when the number country is unsupported rather than billing a surprise route.

Store the country with the user profile. Retrying a UK number on a Ghana-only assumption is a common staging-vs-production bug.

Try SplitSMS free

Send bulk SMS, OTP, and campaigns — 5 free credits on signup.

Newsletter

Subscribe to our newsletter

Delivery tips, SmartForms, and product updates from SplitSMS. No daily spam.

Occasional updates only. Unsubscribe any time.