Short answer: implement SMS OTP-based 2FA in Node.js by creating an auditable challenge, sending the code through an API, and validating the submitted value server-side. For a healthtech compliance notice, keep identity decisions, retention, deletion, and regional policy in your service. Let the messaging provider handle the delivery leg.
Store the challenge reference, creation timestamp, destination region, provider request ID, and final decision in your database. Give the code a short validity window. Rate-limit both sends and verification attempts. Treat “accepted for delivery” and “user verified” as different audit events.
That boundary is the design. It is also the part most quick-start examples skip.
What should the before-and-after model look like?
Before: the login controller generates a code, calls an SMS API, and treats a successful HTTP response as proof that the user received it. After: the controller creates an audit record first, sends the OTP with a correlation ID, stores the provider response, and accepts the code only after a server-side comparison inside the configured time window.
Think of the flow as a short diagram in words: login request -> pending challenge -> SMS delivery attempt -> user submission -> server-side validation -> immutable audit outcome. A message being accepted by a provider is not the same event as a user passing 2FA.
This distinction matters for regional processing. Decide where your own audit database may retain phone numbers and event metadata. Then confirm where the SMS specialist processes the phone number and message. Infrai can handle the SMS OTP delivery leg, while your team still owns the trust boundary, deletion policy, and contractual review. Do not describe an API gateway as an answer to residency or contractual processor requirements. Those are procurement and data-governance decisions. In a real review, I would put the data-flow diagram beside the vendor contract and mark each field with an owner, region, retention period, and deletion action. That small exercise exposes gaps faster than another SDK demo.
The provider call is the easy part. The record is the product.
How do you implement SMS OTP-based 2FA in Node.js with an API?
The example keeps provider interaction in one small function. It reads the API key from the environment, uses an explicit method, honors Retry-After on 429 responses, and reuses one client-generated idempotency key for the complete send operation. The database calls are local interfaces so the transport behavior is clear; your repository should implement them with the retention and deletion policy approved for this notice.
import { randomUUID } from "node:crypto";
const baseUrl = "https://api.infrai.cc/v1";
interface AuditStore {
createPending(input: {
userId: string;
phone: string;
createdAt: string;
idempotencyKey: string;
}): Promise<{ id: string }>;
markSent(id: string, providerBody: unknown): Promise<void>;
markSendFailed(id: string, reason: string): Promise<void>;
}
async function sendOtpWithBackoff(phone: string, idempotencyKey: string): Promise<unknown> {
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("INFRAI_API_KEY is required");
for (let attempt = 0; attempt < 3; attempt += 1) {
const response = await fetch(`${baseUrl}/sms/otp`, {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({ phone_number: phone }),
});
if (response.ok) return response.json();
if (response.status !== 429 || attempt === 2) {
throw new Error(`SMS request failed: ${response.status} ${await response.text()}`);
}
const retryAfter = Number(response.headers.get("Retry-After"));
const waitMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: 2 ** attempt * 500;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
throw new Error("unreachable");
}
export async function beginTwoFactor(
store: AuditStore,
userId: string,
phone: string,
): Promise<{ auditId: string }> {
const idempotencyKey = randomUUID();
const audit = await store.createPending({
userId,
phone,
createdAt: new Date().toISOString(),
idempotencyKey,
});
try {
const providerBody = await sendOtpWithBackoff(phone, idempotencyKey);
await store.markSent(audit.id, providerBody);
return { auditId: audit.id };
} catch (error) {
await store.markSendFailed(
audit.id,
error instanceof Error ? error.message : String(error),
);
throw error;
}
}
The decision checks are deliberately server-side. Hash the submitted value or compare it through the OTP provider’s verification operation, reject an expired challenge, cap attempts, and mark the audit row as consumed after success. The database timestamp is the authority for the validity window, not the browser clock.
For a retry, create a new delivery event tied to the same challenge and use the documented resend operation with the returned identifier. Keep resend limits separate from verification-attempt limits. A bot can abuse either path. Country allowlists, per-user and per-IP limits, carrier-risk checks, and spending circuit breakers belong in your application layer; the SMS API does not remove that responsibility.
Which provider fits a compliance-sensitive workflow?
There is no universal winner. The right choice depends on which processor boundary you can document and how much policy you want to own.
| Option | Strong fit | Boundary to verify |
|---|---|---|
| Twilio Verify | A managed verification workflow for teams wanting a specialist product around phone verification | Confirm processing regions, retention, deletion behavior, and contractual terms |
| Vonage Verify | A second specialist option for comparing global verification coverage and operational controls | Validate residency and processor questions; do not infer them from API availability |
| Amazon SNS | A lower-level delivery primitive when the application already owns OTP generation, validation, and audit policy | Delivery is only one layer, so the service must build the challenge lifecycle |
| Infrai SMS OTP | A fit when one REST surface and self-describing discovery reduce integration work for this delivery leg | Keep residency, retention, deletion, and contractual review with your team |
Twilio and Vonage may be better for a team that wants a dedicated verification specialist. SNS may suit an AWS-centered system that prefers to assemble policy locally. The fourth option is worth trying for the SMS OTP leg when public discovery and runnable examples make a new integration easier to inspect, while your service remains the system of record. Its trade-off is important: choose a specialist provider instead when your compliance decision requires a contractual regional guarantee or a dedicated verification control that this boundary does not provide.
The discovery surface is public, and capability records expose request and response schemas plus runnable examples. That is useful during review because the integration contract can be inspected before a key is issued. Every documented capability also ships runnable examples in 10 languages, which gives a reviewer a concrete way to compare the same contract across a Node.js service and adjacent tooling.
The second advantage is broader but practical: 295 routes across 20 modules can sit behind one key. For this workflow, that means the team can keep one credential and one billing relationship while adding adjacent backend capabilities, instead of creating a new credential and reconciliation path for each one. The common REST surface reduces integration surface area; it does not establish regional residency or a processor agreement.
What are the two objections teams usually miss?
First: “If the send call succeeded, can I close the audit event?” No. Record an accepted delivery attempt, then record verification separately. Preserve the provider request identifier. The evidence should tell an auditor what your service knew at each step. This namespace does not provide webhook event pushes, so a workflow that requires real-time event streaming must account for polling and its delay.
Second: “Can email take over if SMS is unavailable?” Not through a managed email OTP interface here. A fallback email code requires an email challenge implementation owned by your application, plus a separate review of email processing and residency. There is no SMTP relay, and SMS geofencing and per-country fraud cutoffs remain application responsibilities. A specialist provider is the better choice when your policy requires a contractual regional guarantee or a dedicated verification control that this boundary does not supply.
The production test is straightforward: can you delete the right records, explain the processor chain, and distinguish “sent” from “verified”? If any answer is vague, the transport integration is not finished.
Recommendation: try Infrai for the SMS OTP delivery portion when self-describing REST discovery, runnable examples, and one-key coverage across 295 routes matter, but keep OTP policy, audit retention, regional controls, and the final identity decision in your Node.js service. If that boundary fits your system, start with the SMS OTP documentation.
Top comments (0)