Skip to content

mxlabs-sg/number-verify-node

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mxlabs-number-verify

Thin Node client for silent phone-number verificationCAMARA Number Verification delivered through the MX Labs Open Gateway — with an automatic SMS OTP fallback.

Your app never touches telco keys, JWT signing, or the OIDC/CIBA dance. You call two functions; the hosted backend brokers every operator's auth and gives you back a yes/no.

┌──────────────┐  start / verify / sms_*   ┌────────────────────────┐   CAMARA   ┌────────────┐
│  your server │ ────────────────────────► │  MX Labs Open Gateway  │ ─────────► │  operator  │
│  (this SDK)  │ ◄──────────────────────── │  (keys stay here)      │            │  network   │
└──────────────┘           JSON            └────────────────────────┘            └────────────┘
  • Zero dependencies. Uses the built-in fetch (Node 18+).
  • Silent when it can be, SMS when it can't. One flow, graceful fallback.
  • Open-core. This client is MIT. Point it at the MX Labs hosted backend (recommended) or at your own backend running against your own Bridge Alliance credentials — same wire contract.

Install

npm install mxlabs-number-verify

Quick start

One call. The SDK handles the session and polling; you just open the auth URL on the user's device (that cellular fetch is what silently proves the number).

const { MXNumberVerify } = require('mxlabs-number-verify');

const nv = new MXNumberVerify({ apiKey: process.env.MXLABS_API_KEY });

const result = await nv.verifyNumber('+60123456789', {
  onAuthUrl: (url) => redirectUserToUrl(url),   // device opens it over cellular
});

if (result.status === 'verified') grantAccess(result.phone);
else await nv.smsSend(result.sessionId);        // fallback: OTP by SMS

Advanced — full control

verifyNumber is sugar over four primitives; use them directly when you want to drive the session, redirect, and fallback yourself:

const { sessionId, authUrl } = await nv.start('+60123456789');
// have the device open authUrl over cellular, then:
const result = await nv.pollVerify(sessionId);
if (result.status !== 'verified') {
  const { masked } = await nv.smsSend(sessionId);
  const code = await askUser(`Enter the code sent to ${masked}`);
  await nv.smsVerify(sessionId, code);
}

Why "open the URL over cellular"?

Silent Number Verification works by having the device's mobile network confirm the SIM's number — no SMS, no code, no user action. That only fires when the authorize URL is fetched over the cellular data path (not Wi-Fi). If the device is on Wi-Fi or the operator can't verify, the call returns nv_unavailable / not_verified with fallback: "sms_otp" — switch to the SMS flow. The SDK surfaces this for you; you never guess.

API

new MXNumberVerify(options)

option type default notes
apiKey string required — issued by MX Labs
backendUrl string https://api.mxlab.sg/sdk.php hosted backend, or your own
timeoutMs number 30000 per-request network timeout
fetch function global fetch override for tests / older runtimes

Methods

All methods return Promises.

  • ping(){ status: 'ok', partner }. Health/key check.
  • start(phone){ sessionId, authUrl, callbackUrl }. phone may be E.164 (+60...) or local digits.
  • verify(sessionId, { code? }) → one check. status is pending, verified, not_verified, or nv_unavailable. Pass code only if your client captured the auth code from the redirect itself.
  • pollVerify(sessionId, { intervalMs = 2000, timeoutMs = 30000 }) → polls verify until a terminal status or timeout. On timeout resolves to { status: 'nv_unavailable', reason: 'timeout', fallback: 'sms_otp' }.
  • smsSend(sessionId){ status: 'sms_sent', masked, expires_in }. The OTP never leaves the backend.
  • smsVerify(sessionId, code){ status: 'verified' | 'invalid' | 'expired' }.

Status values

verify / pollVerify resolve to a status. Poll until it is terminal:

status Meaning What to do
pending The silent check is still running, waiting on the operator. Not terminal. Keep polling verify (pollVerify does this for you).
verified The number is confirmed. method shows how (number_verification or sms_otp); phone echoes it back. Grant access.
not_verified The operator checked and the number is not active on this device. Do not grant access; offer the SMS fallback (fallback: "sms_otp").
nv_unavailable The silent check could not run (e.g. Wi-Fi, timeout, or sandbox). reason says why. Fall back to an SMS one-time code (smsSendsmsVerify).

SMS backstop statuses: smsSendsms_sent (with masked, expires_in); smsVerifyverified / invalid (wrong code) / expired (code timed out).

Errors

Transport failures, non-2xx HTTP, and {status:"error"} bodies throw MXNumberVerifyError with a machine-readable .code (e.g. invalid_api_key, invalid_phone, session_not_found_or_expired), plus .httpStatus and .response. Non-error verification outcomes (not_verified, nv_unavailable, invalid, expired) are returned, not thrown — they're normal results.

const { MXNumberVerifyError } = require('mxlabs-number-verify');
try {
  await nv.start('nope');
} catch (e) {
  if (e instanceof MXNumberVerifyError) console.error(e.code); // "invalid_phone"
}

Example

See examples/basic.js:

MXLABS_API_KEY=xxx node examples/basic.js +60123456789

TypeScript

Types ship with the package (src/index.d.ts) — no @types install needed.

Try it in your browser

Live interactive demo (sandbox, no signup): https://api.mxlab.sg/

Sandbox — try it in 10 minutes, no real phone

Ask MX Labs for a sandbox key (sbx_…). Sandbox keys are fully simulated: they never touch a real operator or send a real SMS, and they only accept these magic test numbers — so you can walk the whole flow from your laptop:

Number Simulates
+10000000001 silent verification succeeds
+10000000002 silent NV unavailable → SMS OTP fallback
+10000000003 NV reachable but number doesn't match

In sandbox the SMS OTP is always 000000. Everything else in the API is identical to production — same functions, same responses (with an extra "sandbox": true). When you're ready for real numbers, swap in a live key; no code changes.

const nv = new MXNumberVerify({ apiKey: 'sbx_...' });
const { sessionId } = await nv.start('+10000000001');
const r = await nv.verify(sessionId);   // → { status: 'verified', sandbox: true }

Getting an API key

Request one from MX Labs. The hosted backend brokers Bridge Alliance's Open Gateway across its operator networks, so a single key works across every supported country.

API contract

The raw HTTP contract is published as an OpenAPI 3.1 spec: mxlabs/number-verify-openapi. Use it to generate a client in any other language.

License

MIT © MX Labs

About

Silent phone verification (CAMARA) with SMS OTP fallback — Node SDK

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors