Failed OTP deliveries usually have a small set of causes: carrier filtering, the target service declining a number, a mismatch between the number's country and what the service expects, rate limits on the sending side, and timing mistakes in your own flow. Most of these can be detected and mitigated in code. With OTP Route, an activation that receives no SMS expires and is refunded automatically, so the real cost of a failure is time, which makes a disciplined retry strategy more valuable than any single fix.
This guide is for engineers who run verification in QA, CI or operational workflows and want fewer activations ending in expired.
What "failed" means in a receiving flow
In a receiving gateway, a failure is not an error response. It is silence. The activation is created, the number is entered into the target flow, and no SMS arrives before the activation window closes (typically 20 minutes). The activation moves from pending to expired, an activation.expired webhook fires, and the price is credited back to your wallet.
Because the failure is silent, the first job is to make it observable. Record for every activation when it was created, when the number was submitted to the target flow, and whether and when a code arrived. Without those timestamps, you cannot tell a slow delivery from a broken test.
Why OTPs fail
| Cause | What happens | Where it originates |
|---|---|---|
| Carrier filtering | Messages are delayed or dropped by network-level filtering of application-to-person traffic | Carrier and upstream network |
| Number acceptance | The target service declines to send to certain number types or ranges | Target service policy |
| Country mismatch | The service does not support, or behaves differently for, the number's country | Target service configuration |
| Sender-side rate limits | The service throttles repeated requests from the same account, device or IP | Target service |
| Timing errors | The number is submitted late, or the code is requested after the window | Your workflow |
| Input formatting | The number is entered with the wrong country code or format | Your workflow |
| Broken flow | The test never actually triggers the SMS because of an unrelated bug | Your application or test |
Carrier filtering
Mobile networks filter application-to-person messaging to reduce spam. Legitimate verification messages are occasionally delayed or caught. This is outside your control and outside any gateway's full control, but it tends to vary by route. If one country route degrades for a given service, switching countries often resolves it.
Number acceptance
Many services evaluate the phone number before sending a code. Some decline particular number types, recycled numbers or numbers they have seen recently. The policy belongs to the target service, not to the gateway. The practical response is to retry on a fresh number and, if failures persist, on a different country.
Country mismatch
A service may not support every country, or may route verification differently based on the number's country and the locale of the request. A test that signs up with a US number from an environment configured for another region can behave unpredictably. Align the country with what the flow under test expects.
Sender-side rate limits
Services commonly limit how many codes can be requested in a period from one account, device or network address. A test suite that hammers "resend code" or reuses one test identity across many runs can trigger these limits, and the result looks exactly like a delivery failure.
Timing errors
The activation window starts when the activation is created. If your test allocates a number, then spends several minutes on unrelated setup before submitting it, you have shortened your own window. Requesting the code late, or triggering it twice, adds confusion.
Diagnose before you retry
Before changing anything, answer these questions from your logs and the usage logs in the developer console:
- Did the test actually trigger the SMS? Check application logs or screenshots for the "code sent" step.
- Was the number submitted in the right format? Compare the submitted string with the allocated number, including country code.
- How long between creation and submission? Large gaps point to workflow timing.
- Is the failure isolated to one service and country combination? Concentrated failures point to routing or acceptance. Spread-out failures point to your flow.
- Did the test request the code multiple times? That may have tripped the service's own throttling.
Practical mitigations
Trigger promptly after allocation
Create the activation only when the test is ready to submit the number, not at the start of the suite. Treat the time between POST /v1/activations and submission as a metric and keep it small.
Format numbers exactly
Submit the number in the format the target form expects. If the form has a separate country selector, set it explicitly, and strip or keep the country code as the form requires. Formatting is the cheapest cause to rule out, so check it first in any new test suite.
Align country with the flow
Choose the activation country to match the market the flow is built for. Our guide to country routing for OTP covers how to build a preference list per service.
Respect the service's own limits
Space out code requests, avoid reusing one test identity across many concurrent runs, and do not loop on "resend". One clean request per activation is the target.
Cancel early on known failures
If the test fails before the number is submitted, call POST /v1/activations/{id}/cancel right away. The number is released, the refund lands immediately, and your metrics stay clean.
Prefer webhooks
Webhooks deliver the code as soon as it arrives and free your rate limit for activation requests. See webhook-based OTP capture.
A retry strategy that does not waste time
Retries should change something. Re-requesting a code on the same number repeatedly is the least effective option. A layered policy works better:
| Attempt | Action | Rationale |
|---|---|---|
| 1 | Primary country, new activation | Normal path |
| 2 | Same country, fresh activation and fresh number | Rules out a single declined or filtered number |
| 3 | Next country in the preference list | Rules out a route-level issue |
| 4 | Stop and alert | Likely a flow bug or service-side policy, not delivery |
Do not wait the full activation window before deciding an attempt failed. Set a shorter soft deadline from your own measurements of typical time to code, cancel the activation when it passes, and move on. Cancelled activations that never received an SMS are refunded, so a short soft deadline costs nothing extra.
const API = "https://otproute.com/api/v1";
const headers = {
Authorization: `Bearer ${process.env.OTPROUTE_API_KEY}`,
"Content-Type": "application/json",
};
async function api(method, path, body) {
const res = await fetch(`${API}${path}`, {
method, headers, body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 402) throw new Error("insufficient_balance"); // stop entirely
if (res.status === 409) return { unavailable: true }; // try next route
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// plan: e.g. [{country: "us"}, {country: "us"}, {country: "ca"}]
async function verifyWithRetries(service, plan, submitNumber, waitForCode, softDeadlineMs) {
for (const step of plan) {
const a = await api("POST", "/activations", { service, country: step.country });
if (a.unavailable) continue;
await submitNumber(a.number); // illustrative field names, see /docs
const code = await waitForCode(a.id, softDeadlineMs);
if (code) return { code, country: step.country };
await api("POST", `/activations/${a.id}/cancel`); // refunded if no SMS arrived
}
throw new Error("verification_failed_all_routes");
}
waitForCode can resolve from your webhook store or, in simple setups, by polling GET /v1/activations/{id}. HTTP 429 handling with backoff should wrap these calls in production, as shown in the OTP API integration guide.
Measure receive rates per route
Improvement needs a baseline, and the baseline has to come from your own traffic. Track these per service and country:
| Metric | Why it matters |
|---|---|
| Receive rate | The share of activations that received a code |
| Median and tail time to code | Sets your soft deadline realistically |
| Expiry share | Silent failures, refunded but costly in time |
| Cancellation share | Often indicates test-side failures before submission |
| Retries per success | Shows how much your fallback policy is doing |
Review them weekly, and after any change to the flow under test. A sudden drop on one route with others stable is a routing signal. A drop everywhere at once usually points to your own application.
When a dedicated number is the better tool
Some failures stem from needing continuity rather than a one-time code, for example a test account that must keep the same phone number across runs, or an operational inbox that receives recurring messages. A dedicated virtual number in the United States or Canada keeps a stable line, delivers inbound SMS to the dashboard and your webhook, and can be released when no longer needed.
Responsible use
Improving delivery rates is about making legitimate verification reliable for QA, testing, privacy-preserving sign-ups and operational use. It is not about evading a platform's anti-abuse controls. If a service deliberately declines a number or throttles requests, respect that decision and its terms of service. Never use OTP Route for fraud, spam or abuse. For QA-specific guidance, see OTP for QA testing.
Frequently asked questions
Why did my OTP never arrive?
The most common reasons are a formatting mistake when entering the number, the test not actually triggering the SMS, the target service declining the number or throttling requests, a country mismatch, or carrier filtering. Check your own flow first, then retry on a fresh number or a different country.
Am I charged for OTPs that never arrive?
No. If an activation receives no SMS, it expires at the end of its window, typically 20 minutes, and is refunded to your wallet automatically. Cancelling it earlier refunds it sooner.
Should I click resend if the code does not arrive?
Sparingly. Repeated resend requests can trigger the target service's own rate limits and make things worse. A fresh activation on a new number, or a different country, is usually more effective than resending to the same number.
How long should I wait before retrying?
Base it on your own measured time to code for that service and country. Set a soft deadline somewhat above your typical delivery time, cancel the activation when it passes, and move to the next step in your retry plan.
Does choosing a different country really help?
Often, yes. Delivery and acceptance vary by route, so when one country route underperforms for a given service, a fallback country frequently succeeds. Keep a preference list per service and update it from your own receive-rate data.
Route your first OTP in minutes
Pay as you go · no monthly fee · automatic refunds on unused activations.
Topics: otp not received · failed otp delivery · sms verification failure · otp retry strategy · carrier filtering otp · improve otp receive rate · otp troubleshooting