DEV Community

RemingtonCross5246
RemingtonCross5246

Posted on

2026 Property SMS OTP Login: Polling Status Without Webhooks in US and EU

Short answer: use bounded status polling to improve the tenant's SMS OTP login feedback, but keep authentication decisions and the compliance-notice audit record independent of carrier delivery state.

Choice Tenant feedback Operational load Template ownership Best fit
Bounded polling Updates for a short, fixed window Predictable by policy Application owns OTP and notice versions A small property platform without inbound webhooks
Webhook receiver Can update after the browser leaves Requires a public callback and event handling Application can still own templates Systems already operating durable inbound events
No status checks Only "code sent" and resend timing Lowest moving-part count Application still owns templates Low-volume flows where coarse feedback is acceptable

For a one-person SaaS, I would start with bounded polling. It ships without an inbound event surface, gives the login screen enough evidence to stop claiming that an SMS is still moving after the check window closes, and caps background work. The catch is that polling is a UX mechanism, not proof of authentication and not proof that a tenant read a compliance notice.

Keep those records separate.

Can real-time SMS OTP status polling improve 2FA login UX without a webhook?

The browser should ask your application for a small, normalized state such as queued, sent, delivered, undelivered, or unknown. The application, in turn, checks the messaging service. Don't expose a provider response directly to the browser. That couples the UI to fields you don't own and makes a later provider change leak into login code.

More important, delivery state must not decide whether the OTP is valid. The server validates the submitted code against its own expiry, attempt limit, and single-use policy. A delivered state can improve the words on screen; it cannot authenticate anybody. An unknown state shouldn't invalidate a code that the tenant already received either. OWASP's forgot-password guidance follows the same useful boundary: side-channel codes should be random, stored securely, single use, and expire after an appropriate period.

The transport does not get a vote.

For the property-management case, the login exists so a tenant can open a compliance notice. Consider one concrete record: notice N-184 was rendered from template LEASE-ACCESS-v7 for tenant account T-42; challenge C-913 then protected the portal session. The message transport can report that the code associated with C-913 reached a terminal delivery state, while the authentication service can separately report that the challenge was accepted before its local deadline. Neither event says the notice was viewed. Only the portal's own authenticated view or acknowledgement event can fill that slot, if the applicable policy recognizes such an event. Keeping those identifiers separate lets support answer “why couldn't this tenant sign in?” without letting an OTP log masquerade as notice evidence, and it lets an auditor reconstruct which notice text was presented without retaining the secret code. An SMS delivery update belongs beside that trail, not in place of it. This separation prevents a tempting reporting error — calling a delivered login code a delivered legal notice.

There is no universal polling interval for US and EU recipients. Carrier paths, user expectations, applicable notice rules, and traffic shape vary, and the supplied evidence doesn't establish one regional number. I'm not sure a single country-based default would survive real traffic anyway; telemetry from the actual audience should settle it. Start with an explicit product policy, measure it, and retain the same security behavior in every region.

Fast feels good. Correct wins.

EU privacy starts with application-owned template versions

Template ownership matters because an OTP message and a compliance notice change for different reasons. Keep both as versioned application assets, with separate identifiers and review paths. The OTP template stays short and security-focused. The notice template carries the property-specific content required by the business process. A messaging transport may deliver either message, but it shouldn't become the only place where the exact text or revision history exists.

This pays off during an audit. Given a notice record, the system can identify the template revision and input data used to render it without trying to reconstruct content from a transport log. Given an OTP attempt, it can show challenge creation, verification, expiry, and messaging state without pretending the code text itself belongs in long-lived logs. The revenue-per-hour lens is plain here: own the evidence that differentiates the product; outsource message transport, which doesn't.

Poll budgets put a ceiling on operating cost

The second criterion is bounded work. A polling loop needs a maximum duration, a schedule, cancellation, and one shared result per message. Without those limits, ten open tabs can turn one login into ten independent background loops. They can also keep checking after an OTP expires, when no status update can improve the outcome.

Treat the browser as an observer. The server owns the poll budget and coalesces concurrent requests for the same message. Cache terminal states. Stop on delivered or undelivered, stop when the local deadline passes, and return unknown when the upstream state cannot be resolved inside that window. The UI can then offer a resend according to the authentication policy rather than hammering the status check.

That is the trade: slightly stale feedback in exchange for controlled load and fewer components to operate. It's usually a fair exchange for a solo-maintained login flow, but it isn't free.

A TypeScript model keeps delivery evidence separate

The example below uses an eight-check schedule as a product-policy example, not an industry benchmark. It contains no provider route because the transport adapter owns that detail. The important part is the boundary: this function reports messaging evidence and never returns an authentication verdict.

type DeliveryState =
  | "queued"
  | "sent"
  | "delivered"
  | "undelivered"
  | "unknown";

type DeliveryEvidence = {
  messageId: string;
  state: DeliveryState;
  observedAt: string;
};

interface MessageTransport {
  getDeliveryEvidence(messageId: string): Promise<DeliveryEvidence>;
}

const delaysMs = [500, 750, 1_000, 1_500, 2_000, 3_000, 5_000, 8_000] as const;
const terminalStates = new Set<DeliveryState>(["delivered", "undelivered"]);

const wait = (milliseconds: number, signal: AbortSignal) =>
  new Promise<void>((resolve, reject) => {
    const timer = setTimeout(resolve, milliseconds);
    signal.addEventListener(
      "abort",
      () => {
        clearTimeout(timer);
        reject(signal.reason);
      },
      { once: true },
    );
  });

export async function observeDelivery(
  transport: MessageTransport,
  messageId: string,
  signal: AbortSignal,
): Promise<DeliveryEvidence> {
  let latest: DeliveryEvidence = {
    messageId,
    state: "unknown",
    observedAt: new Date().toISOString(),
  };

  for (const delayMs of delaysMs) {
    await wait(delayMs, signal);
    latest = await transport.getDeliveryEvidence(messageId);
    if (terminalStates.has(latest.state)) return latest;
  }

  return latest;
}
Enter fullscreen mode Exit fullscreen mode

The authentication service remains smaller: issue a random code, store it securely, enforce expiration and single use, rate-limit attempts, and invalidate it after success. The UI might map queued to “Sending code,” sent to “Code sent,” delivered to “Check your messages,” and either undelivered or a local timeout to a neutral recovery choice. It must avoid revealing whether an account exists; OWASP explicitly recommends consistent messages and timing for account-recovery responses to reduce enumeration risk.

Log identifiers and transitions, not the OTP. A useful event can contain the challenge ID, message ID, normalized state, attempt number, template version, and timestamp. Redact phone numbers or store only the minimum representation needed by the operational policy. The exact retention period is a legal and business decision; the available sources do not establish a shared US/EU period for this property workflow.

One more practical detail: deployment should preserve in-flight state across releases. Put the current observation and deadline in shared storage if more than one process can serve the tenant. Otherwise, a weekly deploy can reset the poll budget and create duplicate work. Ship weekly, yes — but make restarts boring.

Rollout triggers favor durable delivery events

Stick with a webhook receiver when delivery changes must continue after the login page closes, message volume makes repeated checks wasteful, or the company already has a durable event-ingestion path with signature verification, deduplication, replay handling, and monitoring. In that environment, another event type may be less work than maintaining thousands of short polling loops. The browser can still poll your own read model or receive a server-pushed update; the provider callback remains private to the backend.

Choose no status checks when the transport does not expose meaningful delivery evidence, the login volume is tiny, or “sent, try again after the timer” is honest enough for the UX. This is also the better option when the team cannot monitor a poller. A silent background mechanism with no latency, terminal-state, or request-volume metrics creates confidence without evidence.

Bounded polling is not suitable when the compliance process treats transport events as regulated records that must arrive even without user activity. Use durable asynchronous ingestion and have counsel define what counts as delivery, access, and acknowledgement. The messaging architecture cannot invent that legal meaning.

For commercial email that may accompany the workflow, classify it separately from a required property notice. The FTC's CAN-SPAM guide covers commercial email requirements and says the law applies to commercial messages, including business-to-business email; it should not be used as a generic rulebook for SMS OTPs or as proof that a tenant received a compliance notice. Different channels deserve different templates, consent decisions, and evidence.

The decision is narrow. Use polling when a short-lived login page needs better feedback and the server can enforce a hard work budget. Keep template versions and notice evidence in the application. Move to durable events when the record must outlive the page.

References

Top comments (0)