Skip to content

SMS & login codes

When WhatsApp can’t reach someone, SMS can. Switchboard sends it through licensed providers, fails over between them, and runs login codes as a service — your product never holds a provider key or sees a code.

Licensed providers only

Every SMS goes through a licensed aggregator. Switchboard never runs its own SIM boxes or phone gateways: that is grey-route traffic, carriers block it, and PTA and TDRA treat it as illegal.

Sending an SMS#

typescript
const { id } = await wa.sms.send({
  to: "+923001234567",
  text: "Your car is ready for pickup",
  category: "transactional", // or "otp", "promotional"
})

const message = await wa.outbound.status(id)
// queued → sent → delivered, or failed / dead
// message.provider says which provider carried it

The same status(id) as WhatsApp sends. The category decides the route, because OTP, transactional and promotional traffic are legally different in Pakistan and the UAE.

Providers#

A provider is switched on by setting its keys. Unconfigured ones are skipped, so a deployment with one provider behaves exactly as it would with five.

ProviderGood forDelivery reportsKeys
SendPKPakistan, local and cheapestPolledSENDPK_API_KEY, SENDPK_SENDER
InfobipGlobal, strong in the GulfPolledINFOBIP_API_KEY, INFOBIP_BASE_URL, INFOBIP_SENDER
TelnyxGlobalSigned webhook (Ed25519)TELNYX_API_KEY, TELNYX_FROM, TELNYX_PUBLIC_KEY
PlivoGlobalPolledPLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, PLIVO_SRC
TwilioEverywhere, last resortSigned webhookTWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM

Why some providers are polled

A delivery report is only trusted if it can be verified. Twilio and Telnyx sign theirs precisely enough to check. Infobip and Plivo don’t publish a scheme precise enough to trust, so Switchboard asks them instead — an unverifiable “failed” would otherwise let anyone trigger a paid fallback.

Routing and failover#

By default, Pakistan goes to SendPK first, then Infobip, Telnyx, Plivo, and Twilio last as the most expensive route. Other countries skip SendPK. The order is configuration, not code:

SMS_ROUTES
{
  "+92":  { "otp": ["sendpk", "telnyx"], "*": ["sendpk", "infobip"] },
  "+971": { "*": ["infobip", "twilio"] },
  "*":    { "*": ["twilio"] }
}
What happensWhen
Next provider is triedThe route fails: down, out of credit, bad key, sender not registered.
Nothing else is triedThe number itself is wrong. Another provider would only charge to say the same.
Provider is benched for 2 minutesFive failures in a row. It goes to the back, never away: if everything is down, trying beats refusing.

Login codes#

typescript
// Send a code. Pass the END USER's IP — the per-IP limit counts it.
await wa.verify.start({ to: phone, clientIp: request.ip })

// They type it in.
const { valid } = await wa.verify.check({ to: phone, code })

The code goes out on WhatsApp first. An SMS copy is queued for twenty seconds later and cancelled if WhatsApp delivers it or the code is used. A number whose last code arrived by SMS starts on SMS next time.

RuleDefault
CodeSix digits from crypto/rand, stored only as an HMAC
Expires10 minutes
Guesses5, then the code is dead
UsesExactly once
A new codeCancels the old one and any copy still queued

Pumping defences#

A public “send me a code” endpoint is how SMS fraud works: codes requested to premium numbers, paid for by the sender. These are on by default.

DefenceDefault
Destinations codes may go to+92, +971
Gap between codes to one number30 seconds
Codes per number per hour5
Codes per IP per hour10
Codes per project per day500
Alarm50+ codes in an hour with under 20% used