# MENA phone numbers in production: E.164, validation and OTP routing

A phone-number field looks harmless until it reaches production. Then a Dubai user pastes `050 123 4567`, a Saudi customer enters a Riyadh landline without `+966`, and a Qatari mobile arrives with a leading zero that does not belong there. A parser may accept all three. That does not mean all three are valid, reachable or owned by the person at the keyboard.

The clean way to build this is to separate four questions: what the user typed, what number it parses to, whether the numbering plan considers that shape possible, and whether the user can prove control of the destination now. Combining those questions into one `isValidPhone()` boolean creates brittle code and misleading support tools.

## Keep one canonical value, not one universal presentation

Store an ordinary public telephone number in E.164 form: a `+`, the country calling code and the national significant number, with no spaces or punctuation. ITU-T Recommendation [E.164 defines the international public telecommunication numbering plan](https://www.itu.int/rec/T-REC-E.164/en); an E.164 number has at most 15 digits, excluding the `+` used in presentation.

A practical record might contain:

```json
{
  "phone_e164": "<E.164 number>",
  "phone_country": "AE",
  "verified_at": "2026-08-18T10:15:00Z",
  "verification_channel": "sms",
  "verification_version": 3
}
```

Treat `phone_country` as parsing and display context, not proof of residence or nationality. Keep the raw user entry only when there is a defined support or audit need, protect it as personal data, and give it a short retention period. Do not use punctuation-stripped national input such as `0501234567` as the database identity; it is ambiguous without a region.

Parsing should use a maintained numbering library such as Google's libphonenumber or a well-maintained port. Supply the selected country when the input lacks `+`. Normalize Unicode decimal digits before parsing if the interface accepts Arabic-Indic input. Reject extensions in mobile OTP fields, even if the general telephone parser supports them.

After parsing, store E.164 but display the library's national format in country-specific screens. A UAE mobile pattern such as `05X XXX XXXX` becomes `+971 5X XXX XXXX`; the domestic trunk `0` returns for national display. Saudi mobile numbers work the same way: `05X XXX XXXX` becomes `+966 5X XXX XXXX`, while a Riyadh fixed-line pattern such as `011 XXX XXXX` drops the trunk zero after `+966`. Qatar is different. Its normal fixed and mobile ranges use eight-digit national significant numbers and the country code `+974`; the [CRA numbering information submitted to the ITU](https://www.itu.int/dms_pub/itu-t/oth/02/02/T02020000AB0002PDFE.pdf) lists mobile services under leading digits 3, 5, 6 and 7, and fixed telephony under 4. Do not invent a Qatari trunk zero.

![Comparison of UAE, Saudi and Qatar national phone formats flowing into E.164 storage and live verification.](https://cdn.hashnode.com/uploads/covers/60ecf4a0fc37a15ec15655e8/c1746595-57e0-4e21-a6c0-565d57ca23af.png align="center")

*One production pipeline for UAE, Saudi and Qatar phone-number formats. Sources: ITU-T E.164, UAE TDRA, Saudi CST and Qatar CRA/ITU. Checked 18 August 2026. Credit: SultanByte original editorial artwork.*

## Validation is not delivery

Numbering metadata can answer whether a string is possible for a region and whether its prefix currently belongs to a known type. It cannot prove that the number is assigned, that the device can receive SMS, or that the current user controls it. Google's [libphonenumber falsehoods list](https://github.com/google/libphonenumber/blob/master/FALSEHOODS.md) is blunt about this: numbers are reassigned, valid numbers can later disconnect, and not every number can receive a text.

Use staged validation:

1.  Parse with explicit region context unless the input starts with `+`.
    
2.  Check "possible" first to give useful length feedback while the user types.
    
3.  On submission, check validity and the product's allowed number types.
    
4.  Perform live verification over an allowed channel.
    

Do not hard-code the current UAE mobile prefixes into a regex. TDRA's current portability guide lists `050`, `052`, `054`, `055`, `056` and `058` as portable mobile ranges, while the older [UAE numbering-plan notification held by the ITU](https://www.itu.int/dms_pub/itu-t/oth/02/02/T02020000DC0001PDFE.pdf) reflects the allocations available when it was filed. That difference is a useful warning: numbering data changes. Pin the library version for reproducible builds, monitor upstream metadata releases, and update it through a tested dependency process.

Regex still has a place for cheap input hygiene and abuse controls. It should not be the authority on national numbering.

## Prefixes do not tell you the serving network

Mobile number portability breaks a common routing shortcut. A UAE `050` number can move away from the operator to which that range was originally allocated; [TDRA explains that customers retain the complete number when moving providers](https://tdra.gov.ae/en/consumer-tool-hub/topics/porting-numbers). Saudi Arabia also supports portability. CST says its central system keeps a database of ported numbers, and its [numbering page](https://www.cst.gov.sa/about/Numbering) covers both mobile and fixed portability. Qatar's [CRA says mobile portability has operated since 2012](https://www.cra.gov.qa/en/Services/Telecommunications/Numbering-Management/Mobile-Number-Portability), allowing a customer to keep the number while switching provider.

This matters when an SMS aggregator asks for operator-specific routing or when delivery analytics group traffic by carrier. Prefix inference is acceptable as a weak fallback label, never as current-carrier truth. Prefer a fresh portability-aware lookup from a provider that covers the target country, then cache it briefly enough to respect changes. Keep routing data outside authentication decisions: a carrier lookup may improve delivery, but it does not establish identity.

OTP delivery also fails for mundane reasons: a number may be fixed-line, disconnected, roaming, temporarily unreachable, filtered by an operator or unable to receive application-to-person traffic. Sender registration and template rules can vary by market and route. Ask each messaging provider for documented coverage, sender requirements and fallback behaviour in the three countries; test those claims on the exact route before launch. Vendor dashboards are evidence about that vendor's attempt, not proof that a person read the code.

SMS should not be the only recovery factor for a valuable account. NIST's current digital-identity guidance treats [PSTN out-of-band authentication as restricted](https://pages.nist.gov/800-63-4/sp800-63b.html#pstnOOB) and tells verifiers to consider signals such as SIM change and number porting. Offer passkeys, saved recovery codes or a reviewed recovery process where the risk warrants it. SultanByte's [recovery-first passkeys guide](https://www.sultanbyte.com/passkeys-mena-apps-recovery-first) covers the wider account design.

## Make verification a server-side state machine

Create a verification session before contacting the messaging provider. Give it a random opaque ID and bind it to the account or pre-account transaction, the normalized destination, purpose, channel and expiry. Suggested states are `pending`, `approved`, `expired`, `cancelled` and `locked`.

The create endpoint should accept an idempotency key. Repeating the same request after a client timeout must return the same active session rather than send another code. Lock on a tuple such as `(subject, purpose, phone_hash, idempotency_key)`, and record the provider attempt against that session. A resend can be a new attempt under the same session, subject to a cooldown, rather than a new verification object.

Do not store plaintext OTPs. Store a keyed digest with the session ID in its input, compare in constant time, expire it quickly and invalidate it after success. Prevent concurrent checks from approving the same session twice by using a transaction or compare-and-swap update. Twilio's documentation says, as a **vendor-specific behaviour**, that its Verify token remains the same during its default validity period until verification succeeds; it also publishes [status-check limits](https://www.twilio.com/docs/verify/api/rate-limits-and-timeouts). Do not assume another provider has the same semantics.

Rate limits need several keys because attackers distribute traffic. Apply a short resend cooldown plus rolling limits per session, normalized destination, account, IP or device risk bucket, and tenant. Limit wrong-code checks separately from sends. Return neutral responses where account enumeration is a concern, but give a signed-in user enough information to fix a typo. A circuit breaker should stop a country, route or tenant when spend or failure ratios jump.

## Observe the route without exposing the subscriber

A useful event includes `verification_session_id`, country, channel, provider, route, attempt number, state transition, latency bucket and a normalized provider error class. It does not need the full phone number or OTP. Create a rotating HMAC of the E.164 value for short-window correlation; encryption or plain hashing is a poor substitute because the space of likely numbers is enumerable. Show only a masked national display value in support tools.

Track accepted sends, provider acknowledgements, delivery receipts where available, approvals, expiry and user abandonment as separate events. A provider "sent" response is not an approval. Break failures down by country, channel, provider and error class, with minimum cohort thresholds so dashboards do not reveal individuals.

## Test the boundaries, not someone's handset

Build fixtures from explicit structures, not memorable real numbers. Each fixture should declare region, input, expected E.164 output, expected national display, possible/valid result and intended type. Include Arabic-Indic digits, pasted punctuation, `00` international prefixes, missing country context, too-short and too-long input, a UAE trunk zero, a Saudi fixed-line area code, and a Qatari number with an erroneous zero.

Separate parser tests from delivery tests. Parser tests run offline against a pinned metadata version. Provider contract tests use documented test credentials or sandbox destinations and assert mapping of provider errors into your internal taxonomy. A small, consented pool of real devices can run controlled end-to-end checks in each country; never send OTPs to randomly generated "valid" numbers. Add state-machine tests for duplicate create calls, concurrent approvals, expired codes, resend cooldowns, ported-carrier changes and provider timeout after an accepted request.

The production invariant is simple enough to put in a review checklist: format is not reachability, reachability is not control, and control of a phone number is not permanent ownership. Store the number cleanly, verify it for the transaction at hand, and give the account a recovery path that survives the number changing hands.

*Cover and infographic credit: SultanByte original editorial artwork.*
