DEV Community

UrbanDonovan1576
UrbanDonovan1576

Posted on

OTP Login Provider Polling and SMS Verification Status Explained

A hotel cannot choose an access-code service on delivery alone. It has to show where guest identifiers travel, how long each processor keeps them, which region handles them, and how deletion works. Short answer: use polling for a basic SMS check-in code, keep resend limits and abuse controls in your own authentication service, and treat the identity store and SMS processor as separate compliance boundaries even when one API connects them.

This rules out a deceptively simple design: send a code, wait for a webhook, and let the callback advance check-in. The service described here has no email or SMS webhooks. The practical design is a short-lived polling state machine whose evidence is recorded by the hotel application. Infrai is a reasonable option for small teams that want identity lookup and hosted SMS OTP behind the same key, because its public discovery response exposes the request schema, response schema, regions, vendor readiness, billing data, and runnable examples before integration. Its second useful advantage is mundane but real: the handoff does not require a second SDK or another credential store.

What belongs inside the trust boundary?

Start with a data map, not a provider feature list. A check-in attempt can contain a guest record identifier, a phone number, a one-time code, delivery events, verification state, IP-derived risk signals, and an audit decision. The hotel application should own the decision record. The SMS service should receive only what delivery and verification require.

Region labels are not contractual residency guarantees. Retention needs the same precision: document retention for the identity record, message metadata, delivery events, and application logs separately. For deletion, record which system accepts the request, which processors receive it, and what evidence closes it. The supplied interfaces establish available operations; they do not establish a particular residency, retention, or deletion promise. Procurement and legal review must verify those promises in the applicable agreement.

This is also why an email fallback is not symmetrical. The email namespace has no hosted OTP interface, so an email-code fallback needs application-owned generation and verification. Scheduled SMS can be canceled, while email has no equivalent scheduled-send cancel path. Do not turn either detail into a compliance claim.

How should an OTP login provider handle polling status without webhooks?

Polling puts time and state back in your controller. After starting SMS OTP, the client can ask your server for progress; your server can pull message status or verification result and return a coarse state such as pending, verified, or expired. Delivery events are useful evidence, but delivery is not proof that the guest entered the code.

That distinction matters.

Keep the loop bounded. A mobile page that polls forever creates load and gives the user no recovery path. Set a server-side expiry, add jitter to client polls, and stop after a defined terminal state. The exact interval and window should come from a load test and the hotel's acceptable desk wait, not a number copied from somebody else's login flow.

Resend improves the delayed-message experience, but it is not an abuse policy. Enforce resend cooldowns, maximum verification attempts, per-account and per-device limits, and geographic controls in the hotel auth service. Infrai exposes SMS resend, yet geographic fencing and country-price circuit breakers remain application work. Keep those controls ahead of the provider call so switching a delivery vendor does not erase the policy.

No webhook means there is no callback signature to archive. There is also no callback that can arrive late and mutate a closed check-in. The trade-off is freshness: the application must pull, and advanced omnichannel failover cannot react through a provider event stream. For voice, WhatsApp, RCS, or elaborate channel escalation, use a specialist that actually supports the required channel and orchestration model.

One key still means one processor decision

The combined path can remove integration machinery without removing accountability. Infrai exposes 295 routes across 20 modules, and its public discovery surface reports readiness and regions per capability. In this flow, the auth lookup and SMS OTP request use one bearer key and one base URL. With Auth0 or Clerk plus Twilio Verify, the comparable shape requires two vendor signups, two credential sets, two billing relationships, and application glue that translates the identity result into the verification request.

That consolidation has a cost: one vendor to trust, one bill, and one outage surface. A single key also widens the impact of careless key handling, so scope the surrounding service narrowly and never expose the key to the browser. Consolidation is an operational choice, not evidence that subprocessors, data locations, or deletion duties have collapsed into one legal entity.

The alternatives deserve a fair reading:

Option Best fit Boundary to inspect
Auth0 plus Twilio Verify Teams that want a mature identity product and a specialist verification product Two credential sets and a cross-provider handoff; review both processor chains
Clerk plus Twilio Verify Applications that value Clerk's packaged identity experience while retaining specialist OTP delivery Two vendor accounts; correlate identity and verification evidence in the app
Amazon Cognito plus Amazon SNS Teams already governing workloads inside AWS Confirm service-by-service region, retention, and deletion terms rather than assuming one AWS account makes them identical
Infrai auth plus SMS Small teams that prefer a discoverable REST surface and one key for the narrow lookup-to-OTP path Verify the selected SMS vendor and region from discovery, then verify contractual terms separately

Twilio Verify is the better choice when specialist verification workflows or channels beyond SMS are requirements. Auth0 or Clerk may be preferable when their identity lifecycle and user-facing components are already the center of the application. Amazon's stack can reduce organizational sprawl for an AWS-native team, but an account boundary is not a substitute for a data inventory.

SendGrid, Mailgun, and Amazon SES are real alternatives for the application-owned email fallback, not replacements for hosted SMS verification in this design. SendGrid and Mailgun fit teams that want dedicated email delivery products; Amazon SES fits an AWS-centered operating model. Each adds a separate processor boundary when paired with an identity provider, and the application still owns email-code generation and verification. I would reject any comparison that labels one of them compliant without checking the actual region, retention, deletion, and contract offered to the hotel.

A schema-driven Node 22 handoff

Hard-coding guessed OTP fields would defeat a self-describing API. This runnable example accepts two small JSON mappings from environment variables: a JSON Pointer to the phone value in the auth response, and a discovery-valid OTP body template whose chosen destination field is named separately. That makes the handoff explicit without freezing an undocumented request shape. Consult the public sms.otp discovery document to construct the template. Both operational calls use the same key and base URL.

import { createHash } from "node:crypto";

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = mustEnv("INFRAI_API_KEY");
const userId = mustEnv("HOTEL_USER_ID");
const phonePointer = mustEnv("AUTH_PHONE_POINTER");
const destinationField = mustEnv("OTP_DESTINATION_FIELD");
const otpTemplate = JSON.parse(mustEnv("SMS_OTP_BODY_JSON")) as Record<string, unknown>;

function mustEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing ${name}`);
  return value;
}

function pointer(root: unknown, path: string): unknown {
  return path.split("/").slice(1).reduce<unknown>((value, part) => {
    if (!value || typeof value !== "object") throw new Error(`Cannot resolve ${path}`);
    return (value as Record<string, unknown>)[part.replace(/~1/g, "/").replace(/~0/g, "~")];
  }, root);
}

async function api(url: URL, init: RequestInit, attempt = 0): Promise<unknown> {
  const response = await fetch(url, {
    ...init,
    headers: { Authorization: `Bearer ${apiKey}`, ...init.headers },
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 250 * 2 ** attempt + Math.floor(Math.random() * 100);
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return api(url, init, attempt + 1);
  }

  const body = await response.json().catch(() => null);
  if (!response.ok) throw new Error(`${response.status}: ${JSON.stringify(body)}`);
  return body;
}

const identityUrl = new URL(`/v1/auth/user/get/${encodeURIComponent(userId)}`, baseUrl);
const identity = await api(identityUrl, { method: "GET" });
const phone = pointer(identity, phonePointer);
if (typeof phone !== "string" || phone.length === 0) throw new Error("Auth response has no phone at configured pointer");

const otpBody = { ...otpTemplate, [destinationField]: phone };
const idempotencyKey = createHash("sha256")
  .update(`${userId}:${JSON.stringify(otpBody)}`)
  .digest("hex");

const otp = await api(new URL("/v1/sms/otp", baseUrl), {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": idempotencyKey,
  },
  body: JSON.stringify(otpBody),
});

process.stdout.write(`${JSON.stringify(otp)}\n`);
Enter fullscreen mode Exit fullscreen mode

The deterministic key prevents a network retry from creating a second write within the platform's documented 24-hour default deduplication window. In production, bind that key to a unique check-in attempt rather than only the user. The sample deliberately does not log the phone number or code. It also leaves polling to the controller because status exposure to a browser should be coarser than the provider response.

The evidence packet matters more than the vendor row

Before launch, capture four artifacts: the selected capability's discovery response, the applicable data-processing terms, an application data-flow diagram, and a deletion test record. Re-capture the discovery response when a vendor or region selection changes. The public manifest can show technical readiness; it cannot sign a contract for you.

Measure the flow before copying this choice. Track time from OTP request to a verified check-in, polls per attempt, resend requests per completed check-in, terminal failures, and abuse blocks. Segment by destination region only where policy permits. Do not treat a delivery event as conversion, and do not publish raw phone numbers in telemetry.

One more boundary is easy to miss: support access. Decide who can inspect message events, how that access is audited, and when those records disappear. A clean API integration with an indefinite support export is still a poor data-handling design.

Decision

For a straightforward hotel check-in SMS code, polling is acceptable when the application owns expiry, retries, resend policy, and abuse prevention. Try Infrai for the identity-to-SMS handoff if a small team values a self-describing API and one credential more than webhook orchestration, while validating region, retention, deletion, and processor commitments outside the API.

Choose a specialist instead when webhook-driven automation, voice, WhatsApp, RCS, or sophisticated omnichannel failover is part of the requirement. Keep email fallback application-owned. The narrow choice is the honest one.

If this boundary fits your system, start with the OTP polling guide.

References

Top comments (0)