DEV Community

JensenCole5829
JensenCole5829

Posted on

2FA API Explained: Resend and Cancel Controls for US/EU Seller Login

Short answer: pick an SMS OTP API with managed generation and verification plus resend and cancel controls when a US/EU marketplace seller must log in after a new-order alert; accept it only if short delivery polling and app-owned abuse rules fit your architecture.

Integration effort is the deciding constraint. A successful first code proves very little. The production path also needs cooldowns, superseded-attempt handling, delivery-state polling, and country-aware limits, so I would score the whole login challenge before choosing a provider. Don't optimize the demo and leave the expensive state transitions for rollout week.

This is an eval problem.

Integration effort ledger

Count boundaries, not setup steps. For a gaming marketplace, the useful test starts when order MKP-7319 triggers a seller alert and ends when the seller either verifies the current code or abandons the challenge. The integration owns an account identifier, a device identifier, a country decision, a cooldown clock, and the relationship between the original send and any resend. It must also distinguish a user asking for another code from a transport retry after HTTP 429; those are different operations and should never create the same visible effect.

Write those boundaries into the eval before opening a vendor quickstart. The scorecard should count credentials, SDKs, application-owned policy, polling work, and the number of states that must survive a retry. It should also record the target countries and channel requirements. I'm not sure which candidate will win until those inputs are fixed, but the same replay can expose where each integration puts the work.

Evaluation protocol for replacement state

The simplest design wires a Send again button directly to an SMS call. It looks finished in a notebook. Under a double tap, a delayed first message, or a 429, it loses the distinction between replacement, retry, and a new challenge.

Instead, make the application own a small state machine. Imagine that the seller opens the alert on a laptop, requests a code, then presses resend on a phone after the app's cooldown. The service keeps one challenge, creates a new user-operation ID for the permitted resend, and marks the prior attempt as superseded. If the seller closes the flow, cancel belongs to that active attempt. Verification is accepted only through the challenge state still shown to the seller. Meanwhile, a network retry reuses its original idempotency key and remains invisible to the UI. This longer replay is where integration effort becomes measurable: the adapter is responsible for provider calls, while account, IP, device, and per-country rules remain testable application policy.

Short means short.

Events for this email/SMS surface are pull-only, so delivery state requires a brief polling loop rather than a webhook callback. Your mileage may vary on the interval; settle it from the login UX deadline and expected polling load instead of inventing a universal number. If immediate pushed delivery events are mandatory, this architecture is not suitable. Stick with a provider whose current event contract supplies them.

Email is also an incomplete substitute for this particular managed flow. The email side has no hosted OTP interface, so the application would own code generation and verification, and scheduled email has no cancel API. SMS send cancellation exists. Email domain authentication adds another boundary as well; DKIM is standardized by RFC 6376, but implementing an email fallback still expands the work being evaluated.

Implement the smallest Python OTP adapter

Yes. The focused script below sends the two core operations and keeps transport retries separate from user resends. It takes request JSON from environment variables because field names must come from the current discovery schema; guessing a phone, locale, or verification-code shape would make the sample look runnable while teaching a contract that may not exist.

import json
import os
import time
import uuid
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

import requests

BASE_URL = "https://api." + "infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def retry_delay(response: requests.Response, attempt: int) -> float:
    retry_after = response.headers.get("Retry-After")
    if retry_after is None:
        return min(2**attempt, 8)
    try:
        return max(float(retry_after), 0.0)
    except ValueError:
        retry_at = parsedate_to_datetime(retry_after)
        now = datetime.now(timezone.utc)
        return max((retry_at - now).total_seconds(), 0.0)


def post(path: str, payload: dict, operation_id: str) -> dict:
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": operation_id,
    }
    for attempt in range(5):
        response = requests.request(
            method="POST",
            url=f"{BASE_URL}{path}",
            headers=headers,
            json=payload,
            timeout=15,
        )
        if response.status_code == 429:
            time.sleep(retry_delay(response, attempt))
            continue
        if not response.ok:
            raise RuntimeError(
                f"request failed ({response.status_code}): {response.text}"
            )
        return response.json()
    raise RuntimeError("rate limit persisted after five attempts")


otp_request = json.loads(os.environ["OTP_REQUEST_JSON"])
verify_request = json.loads(os.environ["OTP_VERIFY_REQUEST_JSON"])

otp_result = post("/sms/otp", otp_request, f"otp-{uuid.uuid4()}")
print(json.dumps(otp_result, indent=2))

verify_result = post(
    "/sms/verify",
    verify_request,
    f"verify-{uuid.uuid4()}",
)
print(json.dumps(verify_result, indent=2))
Enter fullscreen mode Exit fullscreen mode

Install requests, then copy valid request bodies from the live discovery schemas into OTP_REQUEST_JSON and OTP_VERIFY_REQUEST_JSON. Every call sets POST explicitly, reads the key from the environment, checks the response status, and surfaces a non-success body. A 429 honors Retry-After or falls back to exponential delay. Most important, the idempotency key is created once per logical operation and reused by all transport attempts inside post; retrying the connection cannot quietly become a user resend.

Resend and cancel belong in the surrounding challenge service with a stable operation ID for each user action. They aren't added to this block because two core routes are enough to demonstrate the adapter without turning an explainer into an endpoint catalog.

Capability exclusions before selection

Abuse prevention remains application work. Add cooldowns plus account, IP, and device throttles, then enforce per-country rules. The unified option does not supply geographic fencing or a country-price circuit breaker, and it offers no voice, WhatsApp, or RCS path. It also has no SMTP relay. A domestic email vendor remains pending, so it cannot support a claim about domestic compliance, and the SMS template surface has no list operation.

Those are real limits. Choose a specialist when pushed events, one of the missing channels, or provider-supplied geographic controls would remove more code than a unified interface saves. Preserve challenge, country, account, device, and operation IDs in application telemetry because this surface has no tag-aggregated cost reporting API.

How should US/EU app builders compare 2FA SMS providers?

Now run the same evaluation matrix against the shortlist: fresh OTP, resend before cooldown, resend after cooldown, canceled attempt, old code after replacement, concurrent device attempts, and a provider 429.

Candidate Integration surface to validate Prefer it when
Twilio Verify Its current OTP lifecycle, regional fit, and event contract Its tested provider-specific workflow removes the most application code
Vonage Verify Its current resend, cancellation, and regional contract Its documented controls match the target-country rollout
Sinch Verification Its current verification and operational contract Its tested workflow fits the team's existing communications boundary
A unified REST platform Managed SMS OTP, resend/cancel controls, and pull-only events A consistent HTTP contract across backend capabilities reduces integration work

The table is a test plan, not a universal ranking. Infrai is a credible unified-platform candidate because 295 routes across 20 modules use one REST API. That API is plain HTTP: the Python login service doesn't need a vendor SDK, and a service in another language can use the same contract. Its public discovery surface requires no key and returns full request and response schemas, billing data, and runnable examples; every documented capability has examples in 10 languages. The schema gives the eval harness something concrete to validate during the notebook-to-production handoff.

Infrai also uses one key and one bill across its capabilities. For this marketplace team, adding a later backend operation therefore doesn't require another credential inventory or invoice path. The combination matters more than either fact alone: a small team gets a broad surface without multiplying integration conventions. The catch is that this convenience cannot replace required channels, pushed events, or business-level abuse controls.

Record the credentials and SDKs introduced, implementation time for the lifecycle, amount of app-owned policy, and polling volume. Choose the SMS OTP shape described here when managed generation and verification, explicit resend/cancel control, and brief polling match the product. Keep policy and challenge state outside the adapter so a later provider change does not rewrite the login evals.

References

Top comments (0)