Phone validation · Use cases · SMS pumping prevention
Use case
Stop SMS pumping before your app sends a single code.
Start on the free tier
One check before every OTP.
Get your free API keyRead the API documentationHow an SMS pumping attack works
Also called SMS toll fraud or artificial traffic inflation. Any OTP fraud prevention plan starts from these three steps.
Bots trigger the SMS
Messages go to high-fee numbers
A carrier shares the fee
The three steps follow the NCSC Ireland advisory. The UK NCSC adds that the rogue party in the mobile ecosystem may never deliver the message to a handset (NCSC UK). MITRE files the technique as T1496.003, Resource Hijacking: SMS Pumping.
What it costs you
Twilio's outbound price per SMS for seven destinations, read on 3 November 2026. 10,000 sends a day is the volume Abstract's guide uses for its own model.
| Destination | smsDestinationRisk | Per SMS | 1,000 a day | 10,000 a day | 10,000 a day for 30 days |
|---|---|---|---|---|---|
| United States | low | $0.0083 | $8.30 | $83 | $2,490 |
| United Kingdom | low | $0.0560 | $56 | $560 | $16,800 |
| France | low | $0.0798 | $79.80 | $798 | $23,940 |
| Morocco | elevated | $0.2244 | $224.40 | $2,244 | $67,320 |
| Nigeria | high | $0.3868 | $386.80 | $3,868 | $116,040 |
| Madagascar | high | $0.5899 | $589.90 | $5,899 | $176,970 |
| Russia | high | $0.7522 | $752.20 | $7,522 | $225,660 |
Prices come from the Twilio public price CSV. The Nigeria pricing page says it is current as of October 2026. Twilio says prices can change without notice and carrier fees may apply. The totals are our arithmetic, not an incident.
As a cross-check, Healthchecks.io reported about 8,000 texts to Morocco and about $1,800 added to its Twilio bill. 8,000 times $0.2244 is $1,795.20.
France is rated low by country, yet it was the largest country by blocked requests in Prelude's 2025 sample, with 3,198,238 blocked at a 10.87% block rate. In October 2025 Prelude blocked 1.86 million requests in France at a 41.31% block rate, aimed at MVNO prefixes. A country rating cannot see that. The completion count per number block, further down, can. Prelude sells SMS verification and "blocked" is its own classification.
Healthchecks.io, May 2026
Blackpanda case study, March 2026
Warning signs in your own data
Four patterns that show up before the invoice does.
Volume rises without new users
Sends to countries you do not serve
Random numbers share a prefix
OTP completion drops
Sources: NCSC Ireland, Okta Threat Intelligence and the Firebase Auth FAQ.
Why spend caps and provider tools act late
Each of these limits the damage after it has started.
Auto-recharge keeps paying
AWS SNS stops within minutes
Firebase caps skip phone auth
Provider tools decide at send time
Read on 3 November 2026. A check before the send does not replace any of these. It gives you a decision earlier than they can.
Trueguard does not sell SMS
Twilio and Prelude publish guides on SMS pumping, and both bill you per SMS. Trueguard bills per check and works with any SMS provider, so sending more texts does not change what we earn. Keep your provider's Fraud Guard or Protect turned on as well.
The layered defence
Six layers, each with an owner. No single one stops every attack, which is why the order and the owner matter.
Country allowlist
Rate limits
The Trueguard check
A challenge
OTP completion per number block
Email on the same signup
Layer 6 uses the Trueguard email validation API on the same signup. The phone and email endpoints are separate calls, so you make both.
Where the check goes
Form, rate limit, country allowlist, then the check, then your rule, then your SMS provider.
form → rate limit → country allowlist → POST /v2/phone/validation → your rule → your SMS provider
const res = await fetch("https://api.trueguard.io/v2/phone/validation", {
method: "POST",
headers: {
"X-API-KEY": process.env.TRUEGUARD_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({ phone: "+14159929960" })
});
console.log(await res.json());const ALLOWED = new Set(["US", "GB", "DE"]); // countries you serve
const decide = (r) => {
if (!ALLOWED.has(r.location.countryCode)) {
return "block";
}
if (r.risk.status === "invalid" || r.risk.isDisposable) {
return "block";
}
if (r.line.type === "premiumRate" || r.line.smsCapable === false) {
return "block";
}
// high, elevated, or null (country not rated)
if (r.risk.smsDestinationRisk !== "low") {
return "challenge";
}
if (r.line.voipType === "nonFixed") {
return "challenge";
}
if (r.risk.status === "unknown") {
return "challenge";
}
return "send";
};risk.status covers validity, disposable and premium rate only. It does not change for destination risk, range risk or VoIP, so your rule reads smsDestinationRisk and voipType itself. Morocco is rated elevated and Pakistan is not rated, so elevated and null both get a challenge here. If you serve an unrated country, skip the challenge for it.
Count OTP completion per number block
Trueguard never sees whether a code was entered. This runs on your side.
const MIN_SENDS = 20; // your choice
const blockOf = (e164) => e164.slice(0, 8); // "+" and the first 7 digits
const hourKey = () => Math.floor(Date.now() / 3_600_000);
// Atomic counter that expires on its own
const bump = async (key, seconds) => {
const n = await redis.incr(key);
if (n === 1) {
await redis.expire(key, seconds);
}
return n;
};
// Before sending
const canSend = async (e164) => {
const block = blockOf(e164);
if (await redis.exists(`otp:paused:${block}`)) {
return false;
}
const hour = hourKey();
const sent = Number(await redis.get(`otp:sent:${block}:${hour}`)) || 0;
const ok = Number(await redis.get(`otp:ok:${block}:${hour}`)) || 0;
if (sent >= MIN_SENDS && ok / sent < 0.5) {
return false;
}
// Volume rule: more than 50 sends in 10 minutes pauses the block for 1 hour
if ((await bump(`otp:burst:${block}`, 600)) > 50) {
await redis.set(`otp:paused:${block}`, "1", "EX", 3600);
return false;
}
await bump(`otp:sent:${block}:${hour}`, 7200);
return true;
};
// When the user enters the right code
const onVerified = (e164) => bump(`otp:ok:${blockOf(e164)}:${hourKey()}`, 7200);A block is the first 7 digits of the E.164 number. Prelude says 6 to 8 digits and Abstract says 7 to 8. The 0.5 is Firebase's per-region figure applied to a block, and MIN_SENDS is your choice. The volume rule is Prelude's example, more than 50 sends in 10 minutes pauses the block for 1 hour. Every counter is an atomic INCR, so two requests cannot read the same value.
Vendor guidance, not standards
We found no neutral source that publishes a number for this. These are the figures vendors publish, read on 3 November 2026, with whether each one sells SMS.
| Source | Sells SMS | What it says |
|---|---|---|
| Firebase Auth FAQ | Yes, Google | A healthy verified-to-sent rate is commonly 70 to 85%. Below 50% in a region is a common indicator of SMS traffic pumping. |
| Prelude 2025 report | Yes | A prefix is the first 6 to 8 digits. Example rule: more than 50 requests to a prefix in 10 minutes blocks it for 1 hour. |
| Abstract guide | No, sells phone validation | Alert at 10 or more sends per prefix of 7 to 8 digits in an hour. Offices and campuses share prefixes. |
| Vonage Fraud Defender | Yes | Verify conversion alerts are raised at a conversion rate of 35% or below. |
| Twilio Verify docs | Yes | Alert when the conversion rate drops, especially in an unexpected country. No number is given. |
Sources: Firebase, Prelude, Abstract, Vonage and Twilio. Abstract calls its figures a calibration starting point against your own baseline.
What the check costs next to the SMS
Trueguard is $0.0025 a check on the paid plan. Here it is next to the SMS price of each destination.
| Destination | SMS | Check as a share of the SMS | Checks paid for by one blocked SMS |
|---|---|---|---|
| United States | $0.0083 | 30% | 3 |
| United Kingdom | $0.0560 | 4.5% | 22 |
| France | $0.0798 | 3.1% | 31 |
| Morocco | $0.2244 | 1.1% | 89 |
| Nigeria | $0.3868 | 0.65% | 154 |
| Madagascar | $0.5899 | 0.42% | 235 |
| Russia | $0.7522 | 0.33% | 300 |
Twilio Line Type Intelligence is $0.008 a request, about 96% of a US SMS. The Twilio price comes from our Twilio Lookup comparison, read on 2 November 2026.
The check is billed during an attack too. At 10,000 sends a day for 30 days that is 300,000 checks, which is $49.99 plus 280,000 times $0.0025 ($700), so $749.99. The same volume of Morocco SMS at Twilio's price is $67,320. That is why rate limits and the country allowlist run before the check.
What this check does not do
- smsDestinationRisk is country level. Only 24 countries are rated (11 high, 4 elevated, 9 low) and every other country returns null.
- rangeRisk measures how many numbers in the same block appeared on receive-SMS sites in the last 90 days. It does not tell you which blocks attackers use for pumping.
- There is no HLR lookup or network line test, so the check cannot tell you whether a line is in service.
- There is no per-number pumping score. Twilio sells one at $0.025 per lookup outside North America, as listed on our Twilio Lookup comparison.
- A valid number can still be pumped. IPQS makes the same point, that even a valid phone number can be used in an SMS pumping attack. That is why layers 2 and 5 exist.
Run the rule on one number.
Try +14086343220, a number listed on a receive-SMS site.
Or try
Frequently asked questions
Every field is in the phone validation API documentation.
Why did I get a huge SMS bill overnight, and how do I stop SMS pumping?
Bots requested codes to high-fee numbers, and a carrier or aggregator shared the termination fee. Healthchecks.io sent about 8,000 texts to random Moroccan numbers in about 12 hours and added about $1,800 to its Twilio bill. The fix order is a country allowlist, rate limits per number, prefix and device, a check of the number before you send, and a watch on how many codes get entered.
Bots hit my OTP endpoint and IP rate limits don't help, they rotate IPs. What now?
Okta reports that attackers use proxy services to spread source IPs, and Blackpanda describes an attack that defeated per-IP limits with hundreds of addresses sending one or two requests each. Limit by number prefix and by device, and use atomic counters. Then check the number itself with Trueguard. The destination does not rotate, so line type, disposable history and country risk still apply.
Should I only allow SMS to certain countries? Which destinations are risky?
Yes. Start with an allowlist of the countries you serve, in your SMS provider or your own code. In Prelude's 2025 sample the highest block rates were Madagascar at 95.20%, Tajikistan at 89.44% and Angola at 79.65%. Prelude sells SMS verification and the data is its own classification. Trueguard's smsDestinationRisk rates 24 countries and returns null for the rest.
Why didn't my spend cap, budget alert or disabled auto-recharge stop the charges?
Healthchecks had auto-recharge set to add $250 whenever funds ran low. It kept topping up until the card's daily spending limit stopped it. AWS says SNS stops sending within minutes of exceeding the spending quota, and that you might incur costs above it in that window. Firebase lists spend caps for only four services, and phone authentication is not among them, so a budget there alerts and does not pause SMS. Read on 3 November 2026.
Will Twilio, AWS or Firebase refund SMS pumping charges, and how do I ask?
Healthchecks reports that Twilio agreed to refund the full amount after it set free-user SMS to 0, tightened geo permissions and enabled SMS pumping protection. Twilio's Verify Fraud Guard Promise (terms last updated 10 July 2024) credits Verify and SMS fees only when Fraud Guard is in Max Protection Mode, you file a support ticket within 30 days of the end of the calendar month, and the affected numbers did not use Geo Permissions, Safe List or the RiskCheck parameter. We found no refund policy listed in the AWS or Firebase docs we read on 3 November 2026. Open a support case with dates and volumes.
A lookup roughly doubles my per-SMS cost. Is it worth it? Is there anything cheaper?
Twilio Line Type Intelligence is $0.008 a request, about 96% of a $0.0083 US SMS. A Trueguard check is $0.0025 on the paid plan, which is 30% of a US SMS and 1.1% of a Morocco SMS at Twilio's price. Two steps cost nothing and run before any paid check: an allowlist of the countries you serve, and offline number parsing with libphonenumber.
Real users get blocked by rate limits or Fraud Guard. How do I avoid false positives?
Twilio lists false positives under 1% for Standard and under 2% for Max in its Fraud Guard docs. In your own rule, block only invalid, disposable, premium rate and not-SMS-capable numbers. Challenge instead of blocking when the country is elevated or unrated and when the number is non-fixed VoIP. Prefix limits can hit shared ranges, because Abstract notes that offices and campuses share prefixes legitimately.
How do I add a check before Supabase, Firebase, Auth0 or better-auth sends the SMS?
Call the validation endpoint from your server, before the line that triggers the send, and send only when your rule returns send. If your client calls the provider directly, an attacker can skip your check, so also set the provider's own region policy. The provider's setting is the backstop for requests that never touch your server.
Check numbers before you pay to text them.
100 free checks a month, no card. Works with any SMS provider.
Get your free API keyNo credit card required.

