Skip to content

Phone number format

Use international format: country code, then the subscriber number, digits only.

2348020000000
│ └────────── subscriber number
└──────────── country code (234 = Nigeria)

A leading + or 00 is accepted and stripped. These are all the same number:

2348020000000 ✅ canonical
+2348020000000 ✅ accepted
002348020000000 ✅ accepted

The API always returns numbers without a +, so 2348020000000 is what you’ll get back and what you should store.

Nigerian mobile numbers are 0 + 10 digits locally (08020000000), which maps to 234 + those same 10 digits.

function toMSISDN(input) {
const digits = String(input).replace(/\D/g, ''); // strip +, spaces, dashes
if (digits.startsWith('234')) return digits; // 2348020000000
if (digits.startsWith('0')) return '234' + digits.slice(1); // 08020000000
if (digits.length === 10) return '234' + digits; // 8020000000
return digits;
}
toMSISDN('0802 000 0000'); // → '2348020000000'
toMSISDN('+234 802 000 0000') // → '2348020000000'
toMSISDN('8020000000'); // → '2348020000000'
import re
def to_msisdn(raw: str) -> str:
digits = re.sub(r"\D", "", str(raw))
if digits.startswith("234"):
return digits
if digits.startswith("0"):
return "234" + digits[1:]
if len(digits) == 10:
return "234" + digits
return digits

For anything beyond Nigeria, use a real library — libphonenumber — rather than growing your own regex.

to accepts both, so this works:

{ "to": ["2348020000000", 2348030000000] }

Numeric entries are parsed digit-exactly, so a 13-digit MSISDN won’t lose precision to floating point.

That said, send strings. An MSISDN is an identifier, not a quantity — you never do arithmetic on it, and a leading zero in some other country’s format would be silently destroyed by a JSON number. Store them as strings too.

We resolve the network from the number’s prefix, which is how cost_estimate can price a batch before it sends:

"per_operator": {
"mtn": { "recipients": 2, "cost": 6.4 },
"airtel": { "recipients": 1, "cost": 3.2 }
}

A number whose prefix matches no known Nigerian network lands in unknown_operator_count. Those are rejected per-message and not charged — but they also never arrive, so check that field:

if (batch.cost_estimate.unknown_operator_count > 0) {
logger.warn({
unroutable: batch.cost_estimate.unknown_operator_count,
reference_id: batch.reference_id,
}, 'some recipients could not be routed');
}

If a whole batch is unroutable you get no_known_operators and nothing is sent.

Recipients are de-duplicated after normalisation. If you send:

{ "to": ["2348020000000", "08020000000", "+2348020000000"] }

all three normalise to the same MSISDN, so recipients_accepted comes back as 1 and you’re charged once.

This is why recipients_accepted can be lower than the length of the array you sent — it’s not an error, and it’s worth logging when the two disagree.