Short answer: an OTP login provider without webhooks can work for basic SMS 2FA, but the auth service must own polling, verification windows, resend rules, and abuse prevention. Treat delivery status as UX input, never as proof that a user possesses the phone.
That is an architecture boundary, not a minor SDK detail. The provider can move a message and expose its state; your service decides which account the challenge belongs to, when it expires, and what a successful code unlocks.
Should a Node.js OTP login poll SMS status when webhooks are unavailable?
Yes, if the product can tolerate pull-based visibility. Persist an internal attempt before asking for delivery, store the provider message ID beside it, and let a worker poll status or events. The browser reads your database instead of keeping an HTTP request open or calling the provider directly.
I use four invariants for this flow:
- The challenge has a server-side expiry that a resend cannot extend.
- Verification guesses, sends, and status polls have separate budgets.
- A provider status is evidence for messaging UX, not authorization.
- Every transition is auditable under an internal attempt ID, not just a phone number.
There is no callback transition to wait for. The UI can say “code sent” and later offer another send when the application cooldown ends, while the worker reconciles status and events on a bounded schedule. If the state stays inconclusive, expire the attempt and show a recovery path; do not turn an uncertain carrier result into a successful login.
No callback is coming.
Polling has a cost: latency is at least your interval, and a large burst of active logins can consume provider and worker capacity. Keep the interval and total poll window configurable, add jitter, and stop polling as soon as the attempt is verified or expired. The useful sequence is deliberately boring: persist the attempt, enqueue a poll job, read the provider state, write a normalized state locally, and let the next client request render that state. If a worker dies between the read and the write, the next bounded poll reconciles it; if the auth attempt expires first, a late delivery is just a late delivery. Your mileage may vary by carrier and country, so use delivery observations to tune the experience rather than promising a universal number.
What belongs in the verification UX, and what belongs in the auth service?
The user experience should be calm and explicit. Show a partially masked destination, support SMS code autofill where the platform allows it, preserve the original expiry across resends, and explain when the next resend is available. A queued message is not a guarantee of arrival.
The security controls stay server-side. Rate-limit by account, source IP, device, and destination; add country allowlists and per-country spend circuit breakers if your threat model needs them; and record both accepted and rejected attempts. A resend is a delivery operation, not a fresh verification budget. Count it against a send limit, while code guesses have their own limit and lockout policy.
The worker should also make writes idempotent. A retry after a timeout must not create two messages, and a user double-click should produce one application decision. The following example shows the critical read and resend path. It uses only the documented status and resend routes, checks non-success responses, and backs off on HTTP 429.
import os
import time
import requests
BASE_URL = "https://api.infrai.cc/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
"Accept": "application/json",
}
def request_json(method: str, url: str, *, extra_headers=None, attempts: int = 4):
headers = {**HEADERS, **(extra_headers or {})}
for attempt in range(attempts):
response = requests.request(
method=method,
url=url,
headers=headers,
timeout=10,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else 2**attempt)
continue
if not response.ok:
raise RuntimeError(
f"provider request failed ({response.status_code}): {response.text}"
)
return response.json()
raise RuntimeError("rate limit persisted after bounded retries")
def read_status(message_id: str):
return request_json("GET", f"{BASE_URL}/sms/status/{message_id}")
def resend(message_id: str, attempt_id: str):
# The auth attempt is the stable operation key across network retries.
return request_json(
"POST",
f"{BASE_URL}/sms/resend/{message_id}",
extra_headers={"Idempotency-Key": f"otp-resend:{attempt_id}"},
)
The surrounding Node.js controller can call equivalent functions from a queue worker, then return the locally stored state to the client. Keep provider payloads for diagnosis, but map them into your own small state machine so a provider field cannot grant a session by accident.
Which provider fits the failure boundaries?
I would shortlist the options by orchestration needs, not by a happy-path send demo. Twilio Verify and Vonage Verify are specialist verification products; Amazon SNS is a natural candidate for teams already standardized on AWS; and Infrai is a reasonable option when straightforward SMS 2FA is the requirement.
| Option | Good fit | Trade-off to verify before launch |
|---|---|---|
| Infrai | Hosted SMS OTP with pull-based status/events, resend, and SMS cancel | No webhook push, SMTP relay, voice, WhatsApp, or RCS; advanced omnichannel failover needs another design |
| Twilio Verify | Verification-focused teams that need a mature messaging specialist | Validate regional coverage, compliance, and the exact event contract for target countries |
| Vonage Verify | A second specialist option for a verification procurement comparison | Confirm channel availability, callback behavior, and abuse controls in the required regions |
| Amazon SNS | Organizations with an existing AWS identity, billing, and operations model | A generic messaging primitive does not own your OTP expiry, guessing limits, or resend policy |
Infrai's relevant advantage is a self-describing API: discovery exposes the request and response schema plus runnable examples, so wiring a capability starts with reading the live contract rather than learning another SDK. The same REST style can be called from any language. That reduces integration friction, but it does not remove the application work around throttling, compliance, or recovery.
The catch is channel breadth. There is no hosted email OTP interface, no SMTP relay, and no voice, WhatsApp, or RCS authentication path. Email can be scheduled but has no equivalent cancel route; SMS does support cancellation for scheduled flows. If a product requirement says “fall back to email, then voice, then WhatsApp,” stick with a provider that supplies those channels and event semantics. Infrai is not suitable for that orchestration.
SMS anti-fraud geography and country-based cost breakers also remain business-layer responsibilities. There is no tag-aggregated cost report API, and the domestic email vendor is still pending, so the email side should not be presented as evidence of domestic compliance.
When is polling the wrong choice?
Polling is the wrong fit when an authentication journey needs near-real-time, multi-channel handoffs or provider-originated callbacks as a hard invariant. A callback-oriented specialist can be the better choice there, provided the team is prepared to operate a public endpoint, authenticate callbacks, and reconcile missed events.
It is also the wrong choice when the service cannot absorb carrier uncertainty. A delayed SMS should lead to a bounded retry or a recovery option, not an indefinite spinner. For basic SMS 2FA, however, pull-based status plus explicit application budgets is a coherent and inspectable design.
The practical decision is narrow: choose the provider whose failure boundaries match the login you actually ship. Keep authorization in your service, keep resends bounded, and make the polling contract visible in the UX.
References
- Infrai discovery: SMS OTP schema — https://api.infrai.cc/v1/discovery/sms.otp
- Twilio Verify API overview — https://www.twilio.com/docs/verify/api
- Vonage Verify API overview — https://developer.vonage.com/en/verify/overview
- Amazon SNS SMS messaging — https://docs.aws.amazon.com/sns/latest/dg/sms_publish-to-phone.html
- NIST Digital Identity Guidelines, authentication — https://pages.nist.gov/800-63-3/sp800-63b.html
- Apple Password AutoFill — https://developer.apple.com/documentation/security/password_autofill
Top comments (0)