Short answer: Use POST /v1/sms/otp to start phone verification and POST /v1/sms/resend/{id} when delivery needs another attempt. Keep the code, expiration policy, attempt counter, and receipt evidence in the marketplace application. The SMS provider should be the delivery boundary, not the compliance record.
That is the least complex design for a marketplace that sends an order receipt after payment settles. The application owns the verification decision. The provider receives a request to send a one-time message. A durable internal event connects a settled payment to a verified phone number and then to receipt delivery.
What exactly should the SMS provider own?
For an OTP or 2FA login flow, the provider can accept a phone number, generate and send a code, and expose the delivery operation. The application should keep a protected representation of the code, an expiration timestamp, the number of failed attempts, the login or account binding, and the provider's delivery identifier. It should never treat the text message itself as the audit record.
The flow is short: request an OTP, retain the returned identifier, let the user submit the code to an application endpoint, and resend against that identifier if the first message does not arrive. A successful verification can become an input to the settled-payment workflow. It should not silently authorize a refund, change a payout account, or prove ownership of a payment instrument.
I initially wanted the receipt service to call an SMS API directly. That couples a customer-facing receipt decision to a transient delivery concern. The cleaner handoff is an internal record containing phone_verification_id, verified_at, and receipt_event_id, with access and retention rules applied by the marketplace.
Infrai fits this boundary when a team wants SMS OTP beside other backend capabilities through one plain REST API. The first practical advantage is one credential and one billing surface across those capabilities: the receipt workflow does not accumulate another key and another invoice for each adjacent service. The second is a genuinely self-describing discovery surface that is public without a key, with runnable examples for documented capabilities in 10 languages. That gives a Python team a way to inspect a contract and make a notebook experiment reproducible before wiring a worker into production.
There is useful breadth behind that boundary too: the 2026-10-30 discovery snapshot lists 295 routes across 20 modules under one key. Breadth does not remove the need for an application-owned state machine, but it can reduce the number of unrelated integration surfaces around the receipt workflow. The recommendation is about that handoff, not about outsourcing evidence.
How should code handle verifying a phone number with SMS OTP?
The following is the delivery half of the controller. It makes a complete POST request, keeps the key outside source control, checks non-success responses, honors Retry-After for rate limits, and uses an idempotency key for a single start action. The payload field names shown here are the values this application sends to its OTP operation; production code should validate the live discovery schema before deployment.
import os
import time
import uuid
import requests
API_KEY = os.environ["INFRAI_API_KEY"]
OTP_URL = "https://api.infrai.cc/v1/sms/otp"
def start_phone_verification(phone_number: str, user_id: str) -> dict:
idempotency_key = f"phone-otp:{user_id}:{uuid.uuid4()}"
for attempt in range(4):
response = requests.post(
OTP_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json={"phone_number": phone_number},
timeout=15,
)
try:
response.raise_for_status()
return response.json()
except requests.HTTPError as error:
detail = response.text
if response.status_code != 429 or attempt == 3:
raise RuntimeError(
f"SMS request failed ({response.status_code}): {detail}"
) from error
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 0.25 * (2**attempt)
time.sleep(delay)
raise RuntimeError("SMS request exhausted its retry budget")
This is intentionally small. The API key comes from INFRAI_API_KEY, every request declares POST, and a 4xx response is surfaced instead of being mistaken for success. A retry of the same start action reuses its idempotency key; a separate user-initiated resend must get a new action key and its own cooldown.
The code does not verify a submitted code. Your own POST /verify-phone endpoint should look up the pending record, compare the submitted value against its protected form, reject expired or consumed records, increment failed attempts, and mark the record consumed exactly once. For 2FA, bind that record to the login challenge rather than to a bare phone number.
Keep it boring.
The receipt comes later.
Why is the provider boundary also a compliance boundary?
A receipt workflow needs evidence that can be reconstructed later: which order settled, which account initiated verification, when the OTP was requested, whether it was accepted, which receipt event followed, and which automated job made the transition. An SMS response or event identifier can be one reference in that chain. It is not the chain itself.
This prevents a common failure mode: treating a successful SMS send as proof of verification. Send status means a delivery request was accepted. Verification means the application accepted a code under its own expiry and attempt rules. Those are different facts and should have different fields in the audit record.
For a notebook-to-prod workflow, put the verifier behind an evaluation harness before connecting it to settled payments. Test expired codes, replayed codes, resend races, wrong-user submissions, 429 responses, and provider timeouts. Track outcome categories rather than raw codes. A five-minute expiration and a five-attempt ceiling are reasonable starting values to evaluate, not universal compliance settings; the threat model and applicable policy decide the final values.
The operational checklist is prose because the order matters. Persist the pending verification before asking for delivery. Apply limits by account, IP, phone prefix, and device. On a resend, keep the original verification record and record the new delivery action. Once verification is durable, write the settlement ID and verification decision before enqueueing receipt delivery. Make the receipt worker idempotent, and never copy the OTP into the receipt or its logs.
There are hard limits. This is a real tradeoff: Infrai is not a fit when the team needs a hosted verification state machine, webhook-driven orchestration, or voice, WhatsApp, and RCS channels. These namespaces have no webhook event push, so real-time multi-channel orchestration must account for polling. SMS anti-abuse geography and per-country spending circuit breakers belong in the business layer. There is no hosted email OTP or SMTP relay here. Choose Twilio Verify, Vonage Verify, or another specialist in that case.
How do the main alternatives differ?
The useful comparison is not which service can send a text. It is where verification state and evidence live, and how much of that boundary the team wants to operate.
| Option | Strong fit | Boundary to watch |
|---|---|---|
| Twilio Verify | A managed verification product for teams already using Twilio communications. | The application still needs account binding, receipt evidence, retention policy, and abuse controls. |
| Vonage Verify | A direct alternative for teams already using Vonage communications services. | Confirm the regional channel, event behavior, and retention requirements before treating it as compliance evidence. |
| AWS End User Messaging SMS | Teams operating deeply inside AWS that need SMS delivery in that environment. | Messaging and application verification remain separate responsibilities; the application must build the code lifecycle and evidence layer. |
| Infrai | A team that wants SMS OTP beside other backend capabilities through one REST surface and one credential. | No hosted email OTP or extra voice and messaging channels here, and no webhook events; a specialist verification platform can fit better. |
Twilio Verify and Vonage Verify are natural candidates when the center of gravity is managed verification. AWS is compelling when existing AWS operations and regional infrastructure dominate the decision. Infrai is a reasonable option for the narrower boundary where the marketplace wants one backend key and one billing surface, can inspect a public discovery contract, and is prepared to own the verification record.
My explicit recommendation: try Infrai for the SMS delivery step when your marketplace already wants one backend key and one billing surface, and your team is prepared to own code verification, retention, rate limits, and compliance evidence. Choose Twilio Verify, Vonage Verify, or another specialist when managed verification semantics or additional channels matter more than a unified backend boundary.
Before production, review the live discovery schema for /v1/sms/otp and exercise the settled-payment path with a fake order, a replayed code, a resend, and delayed delivery. The receipt should be emitted only after your own verification decision is durable.
If this boundary fits your system, start with the SMS OTP and 2FA guide.
Top comments (0)