All posts

The Sailup team · · 5 min read

How to Send OTP SMS in Ghana: A Developer's Guide

A practical guide to one-time-password SMS in Ghana — sender IDs, message format, segment and encoding rules, delivery webhooks, retry logic, and the code to send your first OTP across MTN, Telecel, and AirtelTigo.

An OTP is the highest-stakes message your product sends. It arrives while a user is staring at a login screen with their thumb hovering, and if it takes forty seconds they abandon the signup. Everything below is about making that not happen in Ghana specifically.

What an OTP SMS is

A one-time password SMS is a short, single-segment text carrying a numeric code that authenticates a user for a single action — signing in, confirming a payment, verifying a phone number. It is transactional rather than marketing traffic, it is time-limited, and it should never be sent to a list. In Ghana it must reach MTN, Telecel, and AirtelTigo subscribers with equal reliability, which in practice means a provider with direct routes to all three.

Send one

With Sailup, an OTP is a single POST:

bash
curl -X POST https://api.sailup.io/v1/sms/ \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "YourBrand",
    "to": ["0201234567"],
    "body": "123456 is your YourBrand verification code. It expires in 5 minutes."
  }'

Local numbers (0201234567) and E.164 (+233201234567) are both accepted, so you do not need to normalise input before sending. The response carries a message ID you should store against the user's verification attempt, and a segment quantity that tells you what the send cost.

Get the message body right

OTP copy is constrained by things that are easy to miss until they cost you money or delivery.

Keep it to one segment

SMS is billed and transmitted per segment. Using the GSM-7 alphabet — plain Latin letters, digits, and basic punctuation — a single segment holds 160 characters. Concatenated messages drop to 153 characters per segment, because seven bytes go to the header that reassembles them.

Include a single emoji, a curly quote, or an accented character and the whole message switches to UCS-2 encoding, where one segment is 70 characters and concatenated segments are 67. A 90-character OTP message is one segment in GSM-7 and two in UCS-2. Straight quotes only.

Put the code first

Both Android and iOS can autofill an SMS code, and users scanning a notification preview see roughly the first line. Lead with the digits, then the context:

123456 is your YourBrand verification code. It expires in 5 minutes.

not

Thank you for signing up with YourBrand! Your verification code is 123456.

Name the brand and state the expiry

The brand name in the body is an anti-phishing signal — it lets a user tell your code from a lookalike. The expiry sets expectations so nobody sits waiting on a code that has already died.

Never include a link

A link in an authentication message trains your users to tap links in texts claiming to be from you, which is the exact behaviour every SMS phishing campaign depends on.

Register a sender ID

A branded sender ID means the message arrives from YourBrand rather than a shortcode or an unfamiliar number. For OTPs this matters more than for any other message type: users are deciding in about a second whether the code is legitimate.

Register the sender ID before you need it. On Sailup you register one per project and it works across all supported Ghanaian networks; the API documentation covers the mechanics. Build the registration into your launch checklist rather than your launch week — an unregistered sender is a delivery problem, not a cosmetic one.

Handle delivery properly

Listen for the webhook, don't poll

Sailup fires a webhook on every state change — queued, sent, delivered, failed. Point it at an endpoint that updates the verification attempt, and you get an accurate picture of whether the code actually landed. Polling a status endpoint in a loop while a user waits is slower, noisier, and costs you the latency you were trying to save.

Set a server-side expiry, and enforce it server-side

Five to ten minutes is the usual window. Store the code hashed with an expiry timestamp and compare on the server. A client-side timer is a countdown animation, not a security control.

Rate-limit resends and attempts

Two limits, both necessary:

  • Resends per number — a cooldown of 30 to 60 seconds before another code can be requested, and a cap per hour. Without this, an attacker can bill you at ₵0.02 per request in a loop.
  • Verification attempts per code — five tries, then invalidate the code and require a new one. A six-digit code is trivially brute-forced if you allow unlimited guesses.

Invalidate on success

The moment a code verifies, expire it. Codes that stay valid until their timer runs out are replayable.

Fail open to a second channel, not to nothing

If the webhook reports failure, offer another route — a voice call, an email, an authenticator app — rather than leaving the user re-tapping "resend" against a network that is not going to deliver.

What OTP traffic costs

OTPs are single-segment by design, so the arithmetic is direct. On Sailup, SMS is ₵0.02 per message to every Ghanaian network, with no monthly fee and no charge for messages that fail to deliver:

Verifications per monthMonthly cost
1,000₵20
10,000₵200
100,000₵2,000

Budget for resends. A realistic model is total sends, not unique users — some proportion of every OTP flow is a second attempt. Since failed messages are not billed, the gap between sends and successful deliveries costs you nothing directly, but it does cost conversions, which is the more expensive number. Full rates are on the pricing page.

A checklist before you ship

  1. Sender ID registered and confirmed on MTN, Telecel, and AirtelTigo
  2. Message body under 160 GSM-7 characters, code first, no links
  3. Codes stored hashed with a server-side expiry
  4. Resend cooldown and hourly cap per number
  5. Attempt limit per code, with invalidation on success
  6. Delivery webhook wired to your verification records
  7. A fallback channel when SMS delivery fails
  8. Test mode used end-to-end before going live

The Sailup API reference covers authentication, the send endpoint, and the webhook payload shapes. If you want to try the flow, an account is free and test mode costs nothing.

Send your first SMS in five minutes.

No setup fees, no contracts — pay only for what you send, and volume discounts when you scale.