# otp.com: full corpus > Guides, product updates, and deep dives on OTP verification across SMS, WhatsApp, Telegram, and Email, from the team behind otp.com. Includes every published blog post, the FAQ, and the changelog, inlined as raw Markdown. Canonical index: https://otp.com/llms.txt --- # Designing the OTP Entry Screen > The message is only half the job. Here is how to design the OTP entry screen: input field, autofill, paste, resend timers, and error states that convert. - URL: https://otp.com/blog/guides/designing-the-otp-entry-screen - Category: Guides - Author: otp.com Team - Published: 2026-07-16 - Tags: ux You spent real effort on the verification message: a clean sender, a tight template, autofill hooks. Then the user taps back into your app and hits the screen where they actually type the code. That screen is where verification is won or lost. A great message delivered to a clumsy input still loses users. Here is how to design the entry screen so the code the user already received turns into an approved verification. ## The input field The single most common mistake is fighting the platform. You do not need a row of six separate boxes with custom focus-jumping logic. That pattern looks tidy in a mockup and breaks autofill, paste, and screen readers in practice. Prefer **one input** that accepts the whole code, with three attributes set: - `inputmode="numeric"` so mobile keyboards open on digits, not letters. - `autocomplete="one-time-code"` so the OS can offer the code it just saw. - `pattern="[0-9]*"` as a fallback hint for older browsers. If you want the segmented look, render the boxes as a visual layer over a single real input, so the browser still sees one field. The rule is simple: never make the user (or the OS) work harder than typing six digits into one place. ## Autofill is the shortest path to approved The fastest verification is the one the user never types. On the web, an input with `autocomplete="one-time-code"` lets iOS Safari surface the code from the incoming SMS as a QuickType suggestion. On native, the levers are the SMS Retriever API and Autofill on Android, and the AutoFill framework on iOS. These depend on how the message is formatted, which is why the message and the screen have to be designed together: the entry field advertises that it wants a one-time code, and the message is shaped so the OS can find one. When autofill works, most users approve in a single tap. Treat it as the primary path, not a nice-to-have, and test it on real devices before launch. ## Paste has to just work Some users copy the code from a notification or another app. If your six-box widget only accepts one character per box, a paste of `123456` lands in the first box and the rest are dropped. A single input handles paste for free. If you keep the segmented look, make sure a paste event fills every box, not just the focused one. ## Resend: a timer, not a spammable button When the code does not arrive, users want to resend. Two things matter here, and both come straight from how the API behaves. First, **resend does not retry the same channel; it advances to the next one**. `POST /otp/resend` moves the same OTP to the next channel in your routing order, so the button copy should promise a new attempt, not a duplicate. "Resend code" is fine; "Send the same SMS again" is wrong. One caveat: if the first channel is WhatsApp, the send returns a `wa.me` link the user taps to receive the code over chat, then enters it in the same input as any other channel. Second, **there is a cooldown**. Calling resend too soon returns `429`, and the fallback chain is finite: when there is no further channel, resend returns `409`. Reflect both in the UI: - Disable the resend button and show a countdown (`Resend in 0:23`) so the user cannot hammer it into a `429`. - Once the chain is exhausted, stop offering resend and switch to an escape hatch: "Still stuck? Try a different method" leading to email or support. ## Error and expiry states A wrong code does not fail the verification immediately. The user gets a few attempts; only when they run out does the OTP move to `failed`. Independently, every code expires after its lifetime, at which point the status becomes `expired` even if attempts remain. Your screen needs to speak both languages: - **Wrong code, attempts left:** clear the field, keep the user on the screen, and say what happened ("That code doesn't match. Try again."). Do not reveal how many attempts remain to a potential attacker, but do reassure a fumbling human. - **Attempts exhausted (`failed`) or timed out (`expired`):** the code is dead. The only way forward is a fresh `POST /otp/send`. Say so plainly ("That code expired. We sent a new one.") and reset the timer. Because the API never returns the code itself, your screen can never "check" the code client-side. Every guess is a `POST /otp/verify` round trip, which is exactly why the states above, not client validation, are your real UX surface. ## Accessibility and the small stuff - Label the field ("Enter the 6-digit code"), do not rely on a placeholder that vanishes on focus. - Autofocus the input on screen load so the keyboard is already up. - Announce errors with `aria-live` so screen-reader users hear the retry prompt. - Show the masked recipient the API returns (`+1****71`) so the user knows which number the code went to, especially after a channel fallback. ## The checklist - One real input, `inputmode="numeric"`, `autocomplete="one-time-code"`. - Autofill tested on a real iPhone and a real Android device. - Paste fills the whole code. - Resend is a disabled-with-countdown button, retired when the chain is exhausted. - Distinct copy for wrong-but-retryable, `failed`, and `expired`. - Field labelled, autofocused, and errors announced. Get the message right and the screen right, and the six digits between "send" and "approved" stop being a place users drop off. --- # How We Generate Our SDKs from One OpenAPI Spec > Why the otp.com client libraries for Node, Python, Go, and PHP are generated from one OpenAPI spec instead of written by hand, and how that stops drift. - URL: https://otp.com/blog/engineering/generating-sdks-from-openapi - Category: Engineering - Author: otp.com Team - Published: 2026-07-16 - Tags: sdks, developer-experience Hand-written client libraries are a maintenance trap. Ship a new field on the API and you now have four libraries to update by hand, four changelogs to write, and four chances to drift apart from what the docs promise and what the client actually sends. So we don't write our SDKs by hand. There is one source of truth, the public **OpenAPI spec**, and the Node.js, Python, Go, and PHP clients are *generated* from it. ## One spec, four clients The API exposes four actions, and so does every SDK: - `send`: send a code (your account routing picks the channel) - `verify`: check the code the user entered - `resend`: retry on the next channel - `status`: look up an OTP's state Generate from the spec and each language gets those four as typed methods (`sendOtp`, `verifyOtp`, `resendOtp`, `getOtpStatus` in Node; `send_otp`, `verify_otp`, … in Python). Auth is the same everywhere: your API key as a Bearer token, base URL `https://api.otp.com/api/v1`. ```ts import { Configuration, OTPApi } from '@otp.com/sdk-node'; const api = new OTPApi(new Configuration({ accessToken: process.env.OTP_API_KEY })); const sent = await api.sendOtp({ sendOtpRequest: { recipient: '+14155552671', locale: 'en' } }); const result = await api.verifyOtp({ verifyOtpRequest: { otpId: sent.otpId, code: '123456' } }); // result.matched === true ``` ## How the pipeline works The flow has one direction: the spec describes the API, a generator turns it into client code for each target language, and those clients get published to their ecosystems (npm, PyPI, Go modules, Composer). Nobody edits the generated output by hand between those steps, because a hand-edit is a drift waiting to happen: the next regeneration would either overwrite it or, worse, be held back to preserve it. The spec is the code's parent, not its sibling. That constraint is the whole point. It means the request and response shapes in every SDK are the same shapes the API actually serves, because they came from the same document the server is built against. ## Why generated beats hand-written - **No drift.** The client's request and response types *are* the spec. If the wire format changes, the types change with it, so a mismatch fails to compile instead of failing in production. - **Full coverage, instantly.** A new parameter or endpoint appears in every SDK the moment the spec does, not whenever someone gets around to porting it. - **One review surface.** Behavior is reviewed once, on the spec, instead of re-litigated in four languages. - **Honest types.** `verify` never returns the code (you match on `otpId`), and the generated types reflect that; you can't accidentally depend on a field that doesn't exist. ## When the API changes Adding a field is the routine case, and it shows the model at its best. The spec gets the field, the clients regenerate, and the new option is available in Node, Python, Go, and PHP in one pass, with its type, not four separate ports landing weeks apart. Because the API is versioned under `/api/v1`, changes are additive within a version, so regenerating picks up new capability without breaking the method calls you already wrote. ## The trade-off Generated code isn't always the most idiomatic code a human would hand-write for a given language, and we take that on purpose: predictable and in-sync beats hand-crafted and drifting. Where ergonomics matter, the method shape stays close to the REST call you'd otherwise make yourself, so the mental model transfers whether you read the SDK or the raw endpoint. ## The spec is public Because the OpenAPI document is public, you are not limited to the four languages we publish. If you work in a language we don't ship, you can point a generator at the same spec and get a typed client of your own, or simply call the [REST API](/docs/api/send) directly, which works everywhere. The generated SDKs are a convenience built on an open contract, not a gate in front of it. ## Getting a client - Node.js: `npm install @otp.com/sdk-node` - Python: `pip install otp-sdk` - Go: `go get github.com/otp-com/sdk-go` - PHP: `composer require otp-com/sdk-php` If your language isn't on the list, the [REST API](/docs/api/send) works everywhere. Building with an AI agent instead of writing integration code? The [MCP server](/docs/mcp) exposes the same four actions as agent tools. --- # OTP vs Passwords, Magic Links, and Passkeys > OTP is one tool among passwords, magic links, authenticator apps, and passkeys. Here is what each one proves, and when a delivered code is the right call. - URL: https://otp.com/blog/guides/otp-vs-passwords-magic-links-passkeys - Category: Guides - Author: otp.com Team - Published: 2026-07-16 - Tags: security "Add OTP" is often the reflex when a product needs to verify a user. Sometimes it is exactly right. Sometimes a magic link, an authenticator app, or a passkey fits better, and reaching for a one-time passcode adds cost and friction you did not need. The way to choose is to be precise about what each method actually proves. ## What an OTP proves A one-time passcode proves **possession of a channel**: the person can receive a message at this phone number or this email address, right now. For SMS, Telegram, or email you send a code, the user reads it and types it back. WhatsApp works the same way with one extra tap: the user opens a chat link to receive the code over chat, then types it back. Either way it is a strong, low-friction signal for "this is the same person who owns this number," which is why OTP dominates signup, phone or email verification, and step-up checks. What it does not prove is long-term identity or resistance to a determined attacker who can intercept the channel. Keep that boundary in mind as you compare. ## OTP vs passwords A password proves **knowledge of a secret**. Its failure modes are well known: reuse across sites, phishing, and database leaks. Because the secret is stored and reused, one breach compromises every place it was used. An OTP proves possession instead of knowledge, and the secret is single-use and short-lived, so there is nothing durable to steal or reuse. For consumer products, a delivered code is usually a better primary factor than a password, and pairing the two (password plus OTP) is the classic two-factor setup. ## OTP vs magic links A magic link is the same idea as an email OTP with a different last step: instead of reading a code and typing it, the user clicks a link that carries a token. Both prove possession of the email inbox. The trade is about flow, not security: - A **link** is one tap on the device that received it, but it breaks when the link opens in a different browser than the app, and deep-linking into a native app is fiddly. - A **code** works across devices: the user can receive it on a phone and type it on a laptop, which is exactly the cross-device case the entry screen handles well. If your users routinely start on one device and finish on another, a code travels better than a link. ## OTP vs authenticator apps (TOTP) An authenticator app generates a time-based code from a secret seeded once at setup. It proves possession of that **seeded device**. Compared with a delivered OTP: - No delivery step, so no carrier latency, no channel cost, and it works offline. - But it requires up-front setup, the user must have the app, and losing the device means a recovery flow. TOTP shines as a second factor for accounts the user logs into repeatedly. It is a poor fit for first-touch verification, where you have no seeded device yet and a delivered code is the only thing that works. ## OTP vs passkeys Passkeys (WebAuthn) bind a credential to a device and are **phishing-resistant** by design: the credential only works on the origin it was created for, so a fake login page cannot harvest it. For high-value, repeat logins, passkeys are the strongest option on this list. The cost is onboarding: the user has to create and sync a passkey, and coverage across older devices is uneven. Passkeys also do not solve the cold-start problem of "is this really your phone number," which is still an OTP job. ## Choosing A quick way to decide: - **Verifying a phone number or email, or onboarding a new user?** OTP. There is no seeded device or passkey yet, and a delivered code is the natural fit. - **Logging a returning user into a high-value account?** Prefer a passkey, or a password plus a second factor. Use OTP as step-up for risky actions. - **Adding a second factor for repeat logins on a schedule?** An authenticator app avoids per-login delivery cost, with OTP as the fallback when the device is lost. - **Consumer product that needs low friction across devices?** A delivered OTP beats a magic link when users switch devices mid-flow. ## Where a delivered OTP fits best When the answer is a delivered code, the hard parts are delivery and reach, not the verify step. That is the problem otp.com solves: one API call reaches the user over SMS, WhatsApp, Telegram, or email, routing picks the channel, and it escalates across channels when one does not get through. For SMS, Telegram, and email that means a delivered code; for WhatsApp the response returns a `wa.me` link the user taps to receive the code over chat, then verifies it the same way. Billing is per verification sent, at a published per-country rate. Pick the method that matches what you actually need to prove, and if that is possession of a channel, make the delivery someone else's problem. --- # REST, SDK, or MCP: Choosing Your OTP Integration > Three ways to call the same OTP API: the raw REST endpoints, a generated SDK, or the MCP server for AI agents, and when each one is the right fit. - URL: https://otp.com/blog/engineering/rest-sdk-or-mcp-choosing-your-otp-integration - Category: Engineering - Author: otp.com Team - Published: 2026-07-15 - Tags: sdks, mcp, developer-experience Every otp.com integration calls the same API and the same four actions, `send`, `verify`, `resend`, and `status`. What changes is *how* you call them. There are three front doors, and the right one depends on your language, your dependency budget, and whether a human or an agent is writing the code. ## The REST API: works everywhere The endpoints under `https://api.otp.com/api/v1` are the ground truth. Any language that can make an HTTPS request can use them: send your API key as a Bearer token and handle the JSON yourself. ```bash curl -X POST https://api.otp.com/api/v1/otp/send \ -H "Authorization: Bearer otp_live_•••" \ -H "Content-Type: application/json" \ -d '{"recipient":"+14155552671","locale":"en"}' # -> { "otp_id": "9f3c1b2a-…", "status": "pending", "channel": "sms" } ``` Reach for REST when: - there's no SDK for your language, or you don't want the dependency, - you're on the edge or a constrained runtime and every kilobyte counts, - you want the exact wire contract with nothing in between. The cost is boilerplate: you own the HTTP client, retries, error parsing, and typing. Nothing shields you from a shape change, though because the API is versioned under `/api/v1`, breaking changes do not land silently. ## An SDK: typed and in sync The [official SDKs](/docs/sdks) for Node.js, Python, Go, and PHP wrap those same endpoints in a typed client. They're generated from the OpenAPI spec, so the methods (`sendOtp`, `verifyOtp`, `resendOtp`, `getOtpStatus`) and their types track the API automatically. ```ts import { Configuration, OTPApi } from '@otp.com/sdk-node'; const api = new OTPApi(new Configuration({ accessToken: process.env.OTP_API_KEY })); const sent = await api.sendOtp({ sendOtpRequest: { recipient: '+14155552671', locale: 'en' } }); const result = await api.verifyOtp({ verifyOtpRequest: { otpId: sent.otpId, code: '123456' } }); // result.matched === true ``` Reach for an SDK when: - your language is one of the four, - you want type safety and autocomplete instead of hand-rolled requests, - you'd rather not maintain your own client as the API evolves. It's the shortest path from `npm install` to a verified user in most codebases, and when the spec adds a field, you regenerate instead of hunting through request code. ## The MCP server: let an agent wire it The [MCP server](/docs/mcp) exposes the four actions as tools for an AI agent. Instead of you writing the integration, an assistant (Claude, Cursor, or your own agent) sends, verifies, resends, and checks status straight from a prompt. You add the server to your MCP client config, give it an `OTP_API_KEY`, and the flow becomes a conversation rather than a code path. Reach for MCP when: - you're building the product with an AI agent in the loop, - you want verification wired up without writing the glue yourself, - the flow lives inside an assistant rather than a traditional backend. ## The same flow, three ways It helps to see that these are not three products, just three ways to make the same calls. "Send a code, then verify it" is: - **REST:** a `POST /otp/send`, then a `POST /otp/verify` with the returned `otp_id`. - **SDK:** `api.sendOtp(...)`, then `api.verifyOtp(...)`, with types on both. - **MCP:** the agent calls the `send_otp` tool, then `verify_otp`, reasoning about the result. Because the model is identical underneath, you can mix doors. A backend can call the SDK for its main flow while an internal agent uses MCP for support tasks, both against the same account and keys. ## Same guarantees, whichever door Whichever you pick: one API key, the same four actions, and the code is never returned, verification always happens against the `otp_id` from `send`. The sandbox works the same way through every door too, a `otp_test_…` key sends nothing, charges nothing, and accepts `123456`, so you can build and test before switching to a live key. You can start with REST, move to an SDK when your language fits, and add MCP when an agent joins the team, without relearning the model. | If you… | Use | | --- | --- | | Have no SDK for your language, or want zero deps | REST | | Work in Node.js, Python, Go, or PHP | SDK | | Are building with an AI agent | MCP | --- # Add OTP Verification to an AI Agent with MCP > Point any MCP client at the otp.com server and your AI agent can send, verify, resend, and check one-time passwords using only your API key. - URL: https://otp.com/blog/engineering/otp-verification-in-an-ai-agent-with-mcp - Category: Engineering - Author: otp.com Team - Published: 2026-07-14 - Tags: mcp, ai Verification is the kind of code nobody wants to own: send a code, wait, check what the user typed, fall back a channel if it fails. It's glue, and someone has to write and maintain it. An AI agent can do that wiring for you. The otp.com **MCP server** exposes verification as agent tools over the [Model Context Protocol](https://modelcontextprotocol.io), so an assistant in Claude Desktop, Claude Code, Cursor, or your own app can run the whole flow from a single prompt. ## No backend, one credential The server is a thin, self-contained client of the same public OTP API. There's nothing to deploy and no secret baked into source: you pass your API key at runtime through an environment variable, and that key is the only credential. ## Setup 1. Create an API key in the panel under **API Keys**. An `otp_live_…` key sends for real; an `otp_test_…` key runs in the sandbox. 2. Point your MCP client at the server: ```json { "mcpServers": { "otp": { "command": "npx", "args": ["-y", "@otp.com/mcp"], "env": { "OTP_API_KEY": "otp_live_your_key_here" } } } } ``` The client launches the server with `npx`; there's nothing to install globally. One optional env var, `OTP_API_BASE_URL`, overrides the default `https://api.otp.com/api/v1` when you want to point the agent at staging or a self-hosted environment. ## The tools One tool per API action: | Tool | Does | Input | |------|------|-------| | `send_otp` | Send a code (routing picks the channel) | `recipient`, `locale?` | | `verify_otp` | Verify what the user entered | `otp_id`, `code` | | `resend_otp` | Resend on the next channel | `otp_id` | | `get_otp_status` | Check an OTP's status | `otp_id` | Same rule as the REST API: the code is never returned. The agent verifies against the `otp_id` from `send_otp`, so a leaked transcript never leaks a usable code. ## Start in the sandbox Before you wire a live key into an agent that can act on its own, use a test key. With an `otp_test_…` key nothing is sent and nothing is charged, and the accepted code is always `123456`. That means you can rehearse the entire conversation, including the fallback and error branches, without touching a real phone or your balance. Once the flow behaves, swap in the `otp_live_…` key. ## What a run looks like With the tools in place, the agent orchestrates the state machine on its own. A typical exchange: 1. The agent calls `send_otp` with the user's number and gets back an `otp_id` and the `channel` routing chose (say, `sms`). 2. It asks the user for the code and calls `verify_otp` with the `otp_id` and what they typed. 3. If the user says nothing arrived, the agent calls `resend_otp`, which advances to the next channel rather than resending the same SMS. If that next channel is a code channel (Telegram, email), it asks for the new code and verifies again. If it is WhatsApp, the response carries an `action_url` (a `wa.me` link) that the agent surfaces so the user opens it, receives the code over chat, and reads it back for `verify_otp`, exactly as on the other channels. If it calls resend too soon it gets a `429` cooldown and simply waits; if the chain is exhausted it gets a `409` and tells the user to try another method. 4. It can call `get_otp_status` at any point to see whether the OTP is `pending`, `approved`, `failed`, or `expired`, and narrate the right next step. "Verify this user before checkout" becomes a prompt, not a sprint. There is no integration branch to write, because the branching lives in the agent's reasoning over four small tools. ## Keep the key where it belongs The API key is a real credential: an `otp_live_…` key sends real messages against your balance. Treat the MCP config the way you treat any secret. Keep it in the client's environment, not in a shared repo or a prompt, and prefer a test key for anything you are still iterating on. Because the server holds the key and the code is never returned, the agent can drive verification without ever seeing a secret it could leak. ## When to reach for MCP MCP is the fastest path when an assistant is already in the loop: an AI-built product, an internal agent that onboards users, or a prototype you want working in one prompt. For a fixed server-to-server backend where no agent is involved, the [official SDKs](/docs/sdks) or the raw REST endpoints are the better fit; they cover the same four actions with less moving machinery. Pick MCP when the agent is the integration, and the SDK when your own code is. Prefer to call the API yourself? The [REST reference](/docs/api/send) and the [official SDKs](/docs/sdks) cover the same four actions. --- # Choosing an OTP Channel: SMS, WhatsApp, Telegram, or Email > A practical guide to choosing between SMS, WhatsApp, Telegram, and email for one-time passcodes, comparing reach, latency, and fallback strategy. - URL: https://otp.com/blog/guides/choosing-an-otp-channel - Category: Guides - Author: otp.com Team - Published: 2026-06-11 - Tags: channels, deliverability Every verification flow starts with the same question: **how do we reach the user?** The four mainstream options (SMS, WhatsApp, Telegram, and email) differ far more than most teams expect, and the wrong default quietly costs you conversion in entire regions. Here is the comparison we wish we'd had when we started. ## The short version | Channel | Reach | Typical latency | Weak spot | | -------- | ------------- | --------------- | -------------------------------- | | SMS | Every phone | 2–10 s | Pumping fraud, carrier filtering | | WhatsApp | ~3 B users | User-driven | Extra tap to receive the code, app needed | | Telegram | ~1 B users | 1–3 s | Smaller reach, geo-skewed | | Email | Every account | 2–30 s | Spam folders, slow to open | ## SMS: the default for a reason SMS still reaches every handset on the planet without an app install, which is why it remains the default OTP channel. But it has two structural problems: - **Pumping fraud.** SMS pumping can turn your signup form into someone else's revenue stream, and the abuse is easy to trigger at scale. - **Carrier filtering.** Routes degrade silently. A code that arrives in 3 seconds today can take 40 seconds tomorrow because an intermediate carrier changed. Treat SMS as the **fallback where app-based channels are available** and the primary channel where messaging apps are rare. ## WhatsApp: the conversion winner where it's installed Where WhatsApp is dominant (Latin America, India, large parts of Europe and MENA), verification happens inside the app the user already lives in. WhatsApp differs from the other channels only in delivery: the code is not sent with the send response. Instead the response returns an `action_url` (a `wa.me` link) and the OTP stays `pending`. The user taps it, WhatsApp opens with a prefilled message they send to your business number, and we reply over chat with their code. The catch: the user must have the app, and there is one extra tap to receive the code. From there it is identical to the other channels: the user enters the code and you call `POST /otp/verify` (the `otp.approved` webhook fires on approval too). ## Telegram: fast and underrated Telegram OTPs arrive nearly instantly. Reach is the constraint, but in markets like Russia, Ukraine, Iran, and parts of Central Asia, Telegram penetration rivals or beats WhatsApp. If your user base skews technical, it's often the best first choice. ## Email: slow but always there Email is universal, but it's the slowest path to a six-digit code: inbox delivery can take half a minute, and the code may land in spam. Use it for low-risk flows (newsletter double opt-in, secondary device confirmation) or as the final fallback. ## Reach is regional, not global The instinct to pick "the best channel" is the mistake. There is no global best; there is only the best channel for *this recipient in this country on this device*. WhatsApp is the obvious first choice in São Paulo and close to useless in Seoul. Telegram is the default in Tehran and a rounding error in Toronto. The same product, shipping worldwide, needs different channel orders per market, which is why hard-coding one channel is the pattern that ages worst. This is also why per-country tuning matters more than picking a favorite. Delivery rates for a given channel swing widely by destination, and the only way to know your numbers is to route by country and watch what actually gets through. ## Latency is a distribution, not a number The "typical latency" column above hides the part that hurts. What frustrates users is not the median; it is the tail, the p95 send that takes 30 seconds while they stare at an empty inbox and retype their number. Telegram has a tighter tail than SMS because there is no carrier chain to degrade, while SMS has the fattest tail because any hop in the route can stall. WhatsApp sits apart on this axis: its timing is user-driven, set by when the user taps the link to open WhatsApp and receive the code. If you measure one thing, measure p95 per channel per country, not the average. ## Putting it together: the cascade The right answer is rarely a single channel. A typical high-conversion cascade looks like this: 1. **Detect** the user's likely channels (country code, prior sessions, device hints). 2. **Send** on the highest-reach code channel for that user: Telegram where present, SMS otherwise. 3. **Fall back** across channels if the code isn't used within a few seconds: a resend advances to the next channel in your order. 4. **Escalate** to email as the tail of the chain. WhatsApp can sit at the tail only in its Template mode; in the link modes its flow starts with the user opening a `wa.me` link, so it works as the first channel and a plain resend skips it anywhere else. The good news is that you do not have to build this branching yourself if your provider routes and falls back for you: one send call picks the channel, and a resend advances to the next one in your order without any channel logic in your code. Your job shrinks to setting the order per market and watching the results. A well-tuned cascade improves both delivery rate and time to code in app-heavy regions. That's the whole game: match the channel to the user, verify delivery, and never let a single carrier decide your signup conversion. --- # What is otp.com? - URL: https://otp.com/faq#01-what-is-otp-com - Type: FAQ otp.com is a developer-first, self-serve OTP verification API. A single endpoint verifies users across SMS, WhatsApp, Telegram, and Email, with automatic channel fallback, and you are billed per verification sent. Every channel delivers a one-time code the user enters; on WhatsApp the user opens a `wa.me` link to receive the code over chat, then verifies it the same way. Pricing is public and per-country, and you can integrate over REST, official SDKs, or an MCP server for AI agents. There are no sales calls, no setup fees, and no monthly minimums. --- # What is OTP delivery used for? - URL: https://otp.com/faq#02-what-is-otp-delivery-for - Type: FAQ A one-time password (OTP) is a short code you send to a user to confirm they control a phone number or email address. It is the standard way to secure sign-ups, logins, two-factor authentication, and sensitive actions such as password resets or payment confirmation. otp.com handles both sides: it delivers the code and verifies the one the user enters, so you do not build or maintain that flow yourself. WhatsApp differs only in delivery: the user opens a link to receive the code over chat, then enters it just like any other channel. --- # How does otp.com pricing work? - URL: https://otp.com/faq#03-how-does-pricing-work - Type: FAQ Pricing is public and per-country, and you are billed per verification sent: one charge per code, at the published rate for its country and channel. Verifying is free, however many attempts your user needs, and a resend counts as a fresh verification. Test keys run in the sandbox, where nothing is delivered and nothing is charged. There are no setup fees and no monthly minimums. New accounts start with $3 in free credits, with no credit card and no sales call required. You can estimate your cost with the calculator on the [Pricing](/pricing) page. --- # Which channels does otp.com offer? - URL: https://otp.com/faq#04-which-channels - Type: FAQ otp.com verifies over four channels: SMS, WhatsApp, Telegram, and Email. All four deliver a code the user enters; on WhatsApp the code arrives over chat after the user opens a `wa.me` link the send returns, then it is verified the same way as every other channel. You do not pick a channel per request: your account routing chooses it from the recipient and your configured channel order, with optional per-country overrides. The API stays a single call, and the response tells you which channel was used. See [Channels & routing](/docs/channels-and-routing) for details. --- # How does the fallback mechanism work? - URL: https://otp.com/faq#05-how-does-fallback-work - Type: FAQ If a channel does not get the code through, or the user never verifies it, a single resend call advances the same OTP to the next channel in your order and returns the new channel. You do not implement any of this logic yourself. WhatsApp depends on its mode: under Template it takes part like any other channel, while in the Code back and Inbound approve modes it works only when it is first in your order, because the user has to open a `wa.me` link to start the chat, so a plain resend skips it further down the chain. When the channel chain is exhausted, the resend call reports that no further channel is available. See [Channels & routing](/docs/channels-and-routing) for details. --- # Is there an MCP server? - URL: https://otp.com/faq#06-mcp-server - Type: FAQ Yes. The otp.com MCP server lets an AI agent send, verify, resend, and check one-time passwords through the Model Context Protocol, using your API key. Point any MCP client (Claude Desktop, Claude Code, Cursor, or your own agent) at `@otp.com/mcp` with `npx`: there is no backend to run and no secret in your source. See the [MCP server](/docs/mcp) docs to set it up. --- # Three ways to verify over WhatsApp - URL: https://otp.com/changelog#2026-10-07-whatsapp-verification-modes - Type: Changelog - Date: 2026-10-07 - Tags: New, Improved WhatsApp can now verify a user in one of three modes, and your account runs exactly one of them. **Code back** is the default and is the flow that already existed: the user opens a `wa.me` link, sends the prefilled message, and we reply with their code. **Inbound approve** keeps the link but drops the code: the user's message approves the OTP by itself, and there is nothing to verify. **Template** drops the link: we push the code straight to the user, so WhatsApp behaves exactly like SMS. Your code never needs the mode's name. A new `completion` field on the send, resend, and status responses says how the OTP finishes: `"code"` means collect a code and call `POST /otp/verify`; `"inbound"` means show the link, then poll `GET /otp/{otp_id}` or take the `otp.approved` webhook, and never show a code input. Read it together with `action_url` and the three modes fall out of two fields. `completion` is frozen on the OTP when it is created, so switching your account's mode never re-points a verification already in flight. The docs now say what resend actually does around WhatsApp. Under Template it is a code channel like any other and a plain resend advances into and out of it. Under the two link modes WhatsApp works as the first channel only: a plain resend skips it anywhere else, and only an explicit `resend {"channel":"whatsapp"}` moves an OTP onto it, with the response carrying the link for your app to show. An OTP already on a link needs an explicit `resend {"channel":"sms"}` to move off it; a plain resend there re-returns the same link. Code back stays on by default. To switch, email [info@otp.com](mailto:info@otp.com). The full guide, with a screenshot of what the user sees in each mode, is at [WhatsApp verification](/docs/whatsapp). --- # Mobile SDKs for iOS, Android, and React Native - URL: https://otp.com/changelog#2026-09-17-mobile-sdks - Type: Changelog - Date: 2026-09-17 - Tags: New Verification can now run on the device. The iOS (`Otp`), Android (`com.otp:sdk-android`), and React Native (`@otp.com/sdk-react-native`) SDKs present a screen, send the code, take the user's input, and hand your app a short-lived `verification_token`. The floors are iOS 15 and Android API 26. That token, not the screen's own result, is the outcome. Your backend exchanges it at the new `POST /verifications/exchange` with your server key, and only that response names the recipient that was verified. A success read off a device you do not control is not evidence: the app authenticates with a publishable key (`otp_pk_live_…`) that is built to ship inside the binary and deliberately cannot read a recipient or exchange a verification. The server SDKs make that call for you from 1.2.0. Install steps and a minimal end-to-end sample per platform are in [Mobile SDKs](/docs/mobile-sdks), and the endpoint is documented at [`POST /verifications/exchange`](/docs/api/exchange). --- # otp.com is live - URL: https://otp.com/changelog#2026-08-19-otp-com-is-live - Type: Changelog - Date: 2026-08-19 - Tags: New otp.com is out of preview and open to everyone. Create an account, generate a key on the **API Keys** screen, and send your first code from the [quickstart](/docs/quickstart) in about five minutes. What you get on day one: - **Four channels behind one call.** SMS, WhatsApp, Telegram, and email. You pass a recipient, not a channel: your routing picks one, with per-country overrides and [automatic fallback](/docs/channels-and-routing) when the first attempt does not land. - **One verify step, every channel.** `POST /otp/verify` closes the loop no matter how the code was delivered, WhatsApp included. The code is never returned by the API. - **A sandbox that costs nothing.** A `otp_test_…` key runs the whole flow without sending a real message or charging anything. The accepted code is `123456`. - **Typed clients and agent tools.** Official [SDKs](/docs/sdks) for Node.js, Python, Go, and PHP, generated from the public OpenAPI spec, plus an [MCP server](/docs/mcp) so an AI agent can run verification itself. - **Signals you can build on.** [Webhooks](/docs/webhooks) for approvals and delivery outcomes, and IP-aware abuse protection when you pass `client_ip`. Everything above is documented under [Docs](/docs). If something is missing from the reference, that is a bug in the docs: tell us and we will fix it. --- # Verify users over WhatsApp - URL: https://otp.com/changelog#2026-08-06-whatsapp-click-to-chat - Type: Changelog - Date: 2026-08-06 - Tags: Improved WhatsApp verification works like every other channel: the user enters a code and you call `POST /otp/verify`. The only difference is delivery. When your routing selects WhatsApp, `POST /otp/send` returns an `action_url` (a `wa.me` link) and the OTP stays `pending`. The user opens it and sends the prefilled message, we reply over WhatsApp with their code, and they enter it to verify, exactly as on SMS, email, or Telegram. There is no per-channel branching: show a code input and call verify on every channel. The [`otp.approved` webhook](/docs/webhooks) fires on approval if you prefer a push signal. If the user does not have WhatsApp, move the OTP onto SMS with a resend (`{ "channel": "sms" }`). See [Channels and routing](/docs/channels-and-routing#whatsapp). --- # We launched a changelog - URL: https://otp.com/changelog#2026-07-22-changelog-launched - Type: Changelog - Date: 2026-07-22 - Tags: New This page now tracks notable updates to otp.com: new channels, API endpoints, SDK releases, and improvements to the docs and site. Bookmark it or check back here to see what's shipped. Each entry is tagged so you can tell new features from fixes at a glance. --- # MCP server for AI agents - URL: https://otp.com/changelog#2026-07-10-mcp-server - Type: Changelog - Date: 2026-07-10 - Tags: New Point any [MCP](https://modelcontextprotocol.io) client (Claude Desktop, Claude Code, Cursor, or your own agent) at `@otp.com/mcp` and it can send, verify, resend, and check one-time codes through your API key. There is no backend to run and no secret in the source: the key is supplied at runtime. Each API action maps to one tool: `send_otp`, `verify_otp`, `resend_otp`, and `get_otp_status`. See the [MCP docs](/docs/mcp) to wire it up. --- # Official SDKs for Node.js, Python, Go, and PHP - URL: https://otp.com/changelog#2026-06-24-official-sdks - Type: Changelog - Date: 2026-06-24 - Tags: New Typed client libraries are now available for Node.js (`@otp.com/sdk-node`), Python (`otp-sdk`), Go (`github.com/otp-com/sdk-go`), and PHP (`otp-com/sdk-php`). They are generated from the public OpenAPI spec, so they stay in sync with the API and expose the same four actions: send, verify, resend, and status. Full setup and examples in the [SDK docs](/docs/sdks). --- # Automatic channel fallback on resend - URL: https://otp.com/changelog#2026-06-05-channel-fallback - Type: Changelog - Date: 2026-06-05 - Tags: Improved `resend` now moves the code to the next channel in your routing order, so a delivery that stalls on one channel can complete on another without any change to your integration. See how routing and fallback work in [Channels and routing](/docs/channels-and-routing). --- # Sandbox mode with test API keys - URL: https://otp.com/changelog#2026-05-20-sandbox-mode - Type: Changelog - Date: 2026-05-20 - Tags: New Create an `otp_test_…` key to run the full send, verify, resend, and status flow without sending real messages or spending credits. Swap in your `otp_live_…` key when you are ready to go live. Details in the [sandbox docs](/docs/sandbox).