SMS Verification API for Node.js
This is the Node.js integration guide for the SMSBulk SMS verification API: your script buys a dedicated phone number, waits for the verification SMS and reads the code as JSON. Node.js 18 and newer needs no SDK and no dependencies, the built-in fetch is enough, and the full production-ready flow below is copy-paste runnable.
Install: nothing to install
There is no required npm package for the SMSBulk API. It is plain REST and JSON, so Node.js 18+ covers everything with the built-in fetch. Create a key in the dashboard, export it as an environment variable and run the script. If you prefer axios or another HTTP client you already use, every example translates one to one.
# Node.js 18 or newer ships fetch built in, so there is
# nothing to install for the flow below. Zero dependencies.
node --version
# Keep the API key out of your source code:
export SMSBULK_API_KEY=smsbulk_your_key_here
node verify.jsAuthentication in one header
Every authenticated request sends the key as an X-API-Key header. Keep the key in an environment variable or your secret manager, never in source control. Keys are created, rotated and revoked in the dashboard, and the API documentation lists every endpoint the key unlocks.
Production-ready OTP flow in Node.js
The loop is the same three calls the platform is built around: create an activation, poll until the code arrives, confirm it. This version is hardened the way a real service needs: a hard timeout on every request, conservative retries with exponential backoff, and a cancel path so a wait that times out is refunded automatically.
// verify.js (Node.js 18+, zero dependencies)
const BASE = 'https://smsbulk.net/api/v1';
const KEY = process.env.SMSBULK_API_KEY; // smsbulk_...
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
class SmsBulkError extends Error {
constructor(status, body) {
super('SMSBulk API ' + status + ': ' + body);
this.status = status;
}
}
// Retries are deliberately conservative: 429 (rate limit) is always safe to
// retry; network errors and 5xx are retried for GET and DELETE only. A
// failed POST is never retried blindly, so an ambiguous error can never
// buy two numbers.
async function api(path, { method = 'GET', body, timeoutMs = 15000, retries = 3 } = {}) {
const idempotent = method === 'GET' || method === 'DELETE';
for (let attempt = 0; ; attempt++) {
let res;
try {
res = await fetch(BASE + path, {
method,
headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(timeoutMs), // hard per-request timeout
});
} catch (err) {
// Network failure or timeout.
if (idempotent && attempt < retries) {
await sleep(1000 * 2 ** attempt);
continue;
}
throw err;
}
if (res.ok) {
const text = await res.text();
return text ? JSON.parse(text) : null;
}
const errText = await res.text();
const retriable = res.status === 429 || (res.status >= 500 && idempotent);
if (retriable && attempt < retries) {
await sleep(1000 * 2 ** attempt);
continue;
}
throw new SmsBulkError(res.status, errText);
}
}
async function receiveOtp(serviceCode, countryIso, { pollMs = 5000, maxWaitMs = 600000 } = {}) {
// 1) Buy a dedicated number
const activation = await api('/activations', {
method: 'POST',
body: { serviceCode, countryIso },
});
console.log('Number:', activation.phoneNumber);
// 2) Poll until the verification code arrives
const deadline = Date.now() + maxWaitMs;
while (Date.now() < deadline) {
await sleep(pollMs);
const a = await api('/activations/' + activation.id);
if (a.status === 'RECEIVED') {
// 3) Confirm the code was used
await api('/activations/' + activation.id + '/complete', { method: 'POST' });
return a.smsCode;
}
if (['CANCELLED', 'EXPIRED', 'REFUNDED'].includes(a.status)) {
throw new Error('Ended without SMS: ' + a.status);
}
}
// 4) Give up: cancel so the charge returns to your balance automatically.
// Cancellation unlocks a couple of minutes after purchase, so keep
// maxWaitMs comfortably above that window.
await api('/activations/' + activation.id, { method: 'DELETE' });
throw new Error('No SMS within ' + maxWaitMs / 1000 + 's, activation cancelled and refunded');
}
receiveOtp('tg', 'US')
.then((code) => console.log('Verification code:', code))
.catch((err) => {
console.error(err.message);
process.exit(1);
});The request signatures are exactly the ones verified end to end against the production API. Two details worth keeping: a failed POST is never retried blindly (an ambiguous error must not buy two numbers), and cancellation unlocks a couple of minutes after purchase, so keep the wait window above that. Every endpoint and response field is specified in the API documentation.
Polling, not webhooks
The v1 API is polling based: you ask for the activation state until the status flips to RECEIVED and the smsCode field is filled. There is no webhook to configure, which also means nothing to expose publicly and nothing to secure against replay. A five second interval is a good default; most codes arrive within a few polls.
In a bigger Node.js service, run the polling loop in a queue worker or a background job rather than inside an HTTP request handler, and store the activation id so a restarted worker can resume the same activation instead of buying a new number.
Sending SMS from Node.js is a different product
A lot of "send and receive SMS node js" searches are really two different needs. Sending messages to arbitrary phone numbers is an SMS gateway product, and this API does not do that. What it does is the receive side: it gives your Node.js code a real, dedicated number and hands back the verification code that arrives on it.
That receive side is what you need to register accounts without tying them to a personal SIM, to onboard fleets of business profiles, or to run QA against your own signup flow with real numbers. For that last case, the SMS verification testing guide covers the CI angle in depth.
Live starting prices for popular services
Prices are per successful verification and vary by country. These starting prices come from the live catalogue and refresh hourly on this page:
Your code can read the same data: the catalogue endpoints are public, and the service catalogue page lists everything you can verify.
Node.js SMS API FAQ
Should I use axios or the built-in fetch?
For this API the built-in fetch is enough: it supports headers, JSON bodies and, with AbortSignal.timeout, hard timeouts, so the flow above runs with zero dependencies. If axios is already in your stack, use it; interceptors are a natural place for the retry logic. The endpoints and headers are identical either way.
Is there an official npm package or SDK?
No SDK is required and none is planned as a dependency you must take: the API is plain REST and JSON. The flow above is deliberately dependency-less. If you are building an AI agent rather than a classic service, the official MCP server documentation exposes the same operations as native tools.
Does this work with TypeScript?
Yes. Type the activation object once ({ id: string; status: string; phoneNumber: string; smsCode: string | null }) and the whole flow type-checks under strict mode. The response schemas for every endpoint are in the API documentation.
ESM or CommonJS?
Both. The script above uses only globals (fetch, process, setTimeout), no imports at all, so it runs unchanged as a .js CommonJS file or under "type": "module". Drop it into any existing project without touching your module setup.
Can I run many verifications in parallel?
Yes, activations are independent, so Promise.all over receiveOtp calls works. Respect the documented rate limits when you scale up, list activations with cursor pagination, and reconcile costs per activation from the wallet transaction log.
What happens if the code never arrives?
The flow above cancels the activation after the wait window and the charge returns to your balance automatically. You only pay for verifications that deliver a code.
Keep exploring
The SMS verification API page covers the language-neutral API in depth, and the same flow in Python lives at SMS API for Python. Everything else, from error codes to pagination, is in the API documentation.
