SMS pumping, also called artificially inflated traffic (AIT), is fraud in which bots make a business send text messages, such as one-time codes, to numbers where a party in the delivery chain is paid a share of every message: the business pays for each message and nobody reads it. Spaw's default SMS policy is not tuned against it. The defaults refuse the numbers that almost always waste or abuse a code: fictional, disposable, reported for abuse and premium-rate. They let through the ordinary mobiles, the +1 numbers outside the United States and Canada and the international-network numbers that pumping can use. This guide shows what the defaults pass, the one request that tightens them for a code sender, and what your own traffic can and cannot flag. The rules themselves, and the order they run in, are in gate SMS verification codes with the SMS policy.
How the fraud makes money
AWS describes AIT as bots generating large volumes of fake SMS traffic through the forms that trigger a message: one-time passwords, app download links, promotion signups. The profit, in its words, comes from "colluding with parties in the SMS delivery chain to receive a portion of the revenue generated from each fraudulent message sent" (AWS Messaging blog, "Lower Your Risk of SMS Fraud with Country Level Blocking and AWS End User Messaging", 6 June 2024, https://aws.amazon.com/blogs/messaging-and-targeting/lower-your-risk-of-sms-fraud-with-country-level-blocking-and-amazon-pinpoint/, accessed 27 September 2026). A later post names insecure public web forms as the vector most often used, and puts blocking every country a business does not send to first among the defences (AWS Messaging blog, "Defending Against SMS Pumping: New AWS Features to Help Combat Artificially Inflated Traffic", 29 April 2025, https://aws.amazon.com/blogs/messaging-and-targeting/defending-against-sms-pumping-new-aws-features-to-help-combat-artificially-inflated-traffic/, accessed 27 September 2026).
Two things follow for a phone lookup. Nothing in that arrangement needs a special kind of number: the share comes from delivering the message, so the destination can be an ordinary mobile that is valid, sits in a carrier's block and scores 0. And the pattern shows only across many lookups: number after number submitted by one script, in one range or from one source or to a country the business rarely sends to. One answer cannot say a number is pumped. The controls are the destination rules and the counts of your own traffic, and both live in the SMS policy.
Spaw publishes no figure for how common pumping is. The counts that could show it, of ranges, client addresses and destinations, are kept per account and never read for another account, so there is no cross-account pumping count to publish.
SMS pumping and IRSF
SMS pumping is often confused with International Revenue Share Fraud (IRSF). Microsoft's documentation for its multifactor authentication describes IRSF as unauthorized actors who "divert telephony traffic and generate profits through a technique called traffic pumping" (Microsoft Learn, "Understanding telephony fraud risk for Microsoft Entra multifactor authentication", dated 4 March 2025, https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-telephony-fraud, accessed 27 September 2026). Microsoft uses "traffic pumping" for IRSF too; what separates the two here is what is abused and who pays. SMS pumping abuses a business's own message sending, usually to ordinary mobile numbers, and bills that business per message. IRSF mostly pushes calls to premium-rate or international numbers whose termination fee is shared, often through a hijacked phone system. The default premium_rate block refuses only ranges the numbering plan types as premium rate, such as UK 09, and says nothing about where a revenue share is paid. For a lookup the useful split is by kind of destination, because each kind meets a different rule:
| Destination | What the lookup answers | Under the defaults | The rule that refuses it |
|---|---|---|---|
| An ordinary mobile in a country you do not serve | line_type: "mobile", no signals, score 0 |
Sent | allowed_countries, as country |
| A +1 number outside the United States and Canada, such as +1 876 | The region's own country, JM for Jamaica |
Sent | allowed_countries without that region |
| An international network or satellite number: +870, +881, +882, +883 | country: null, international_network 30, plus voip 30 where the plan types it VoIP |
Sent, at 30 or 60 | Any allowed_countries, or international_network in block_signals |
| A premium-rate number, such as UK 09 | premium_rate 60 |
Refused, as premium_rate |
The default |
| A UK 084 or 087 shared-cost number | shared_cost 20 |
Sent | shared_cost in blocked_line_types |
What the defaults let through, scored
The defaults block five signals, fictional, disposable, reported_abuse, reported_abuse_widely and premium_rate, and any score above 60. They send to every country. That leaves the pumping cases below passing, each as the API answers it on 27 September 2026:
| Answer | Signals | Score | ok_to_send under the defaults |
|---|---|---|---|
| A +881 number, or a +870 or +882 number the plan types as mobile or voicemail | international_network 30 | 30, medium | true |
| A +883 number, or a +870 or +882 number the plan types as VoIP | voip 30, international_network 30 | 60, high | true |
| The fifth distinct number your account looked up in one 1,000-number range within the hour, nothing else firing | range_burst 40 | 40, medium | true |
| The fifth distinct number one client address tried within the hour | numbers_per_client 30 | 30, medium | true |
| Both of the last two at once | range_burst 40, numbers_per_client 30 | 70, high | false, blocked_by: "risk_score" |
The ceiling refuses only a score above 60, so an answer can read high and still be sent. libphonenumber, the numbering metadata Spaw starts from (9.0.37 and 9.0.40 carry the same ranges for these codes on 27 September 2026; the release in use is on the status page, under phone numbering metadata), types every +883 range it knows as VoIP, +881 as mobile, and +870 and +882 ranges as mobile or VoIP, with some +882 voicemail ranges. A +883 answer, trimmed:
{
"valid": true,
"country": null,
"line_type": "voip",
"risk_signals": [
{ "signal": "voip", "weight": 30, "dataset": "libphonenumber" },
{ "signal": "international_network", "weight": 30, "dataset": "libphonenumber" }
],
"risk_score": 60,
"risk_level": "high",
"ok_to_send": true,
"blocked_by": null
}
Tightening the policy for a code sender
One request adds a country list and three signals: the two your own traffic raises during a pumping run, and the one that marks international-network numbers. This example is for a service whose customers are in the United States, Canada and Puerto Rico:
curl -X PUT https://spaw.co/api/v1/phone/policy \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"allowed_countries": ["US", "CA", "PR"], "block_signals": ["fictional", "disposable", "reported_abuse", "reported_abuse_widely", "premium_rate", "range_burst", "numbers_per_client", "international_network"]}'
block_signals replaces the list rather than adding to it, which is why the example names the five defaults again. A body naming only the three new signals would let through a premium-rate number, a number your account reported as abused and one that three or more accounts reported, whenever nothing else fires, because they score 60, 60 and 40 and the ceiling refuses only a score above 60. Fictional (80) and disposable (70) numbers would still be refused, but by the score, as risk_score. The answer's data is the policy as stored, with the signals in the weights' order and the countries upper-case and sorted (updated_at left out here):
{
"max_risk_score": 60,
"block_signals": ["fictional", "disposable", "reported_abuse", "reported_abuse_widely", "premium_rate", "range_burst", "numbers_per_client", "international_network"],
"allowed_countries": ["CA", "PR", "US"],
"blocked_countries": null,
"blocked_line_types": [],
"require_reachable": false,
"block_opted_out": true,
"is_default": false
}
Under that policy the cases above change:
| Answer | blocked_by |
|---|---|
| An international-network number | country: a number with no country is outside every allow-list, and the country rule runs before the signals |
The same number, for an account with allowed_countries left null |
international_network |
| A +1 876 number | country |
| The fifth number in one range within the hour | range_burst |
| The fifth number from one client address | numbers_per_client |
| Both at once | range_burst, the first blocked signal in the weights' order |
Send client_ip with every server-side lookup, the address of the person asking for the code; without it numbers_per_client cannot fire. Blocking range_burst refuses every further number in that range while five distinct numbers of it sit in the last 60 minutes, including a real customer's number that happens to be there. A sender who would rather slow the flow than refuse it can leave both signals out of block_signals and add a delay or a challenge whenever an answer carries one. Naming the signals is also narrower than lowering max_risk_score below 40, which would refuse every number in a virtual-number wholesaler's block as well.
The +1 regions outside the United States and Canada
The calling code +1 is shared by 25 regions. libphonenumber 9.0.37 and NANPA's area-code report of 26 September 2026 agree on the 23 outside the United States and Canada and the 27 geographic area codes they use. The answer's country is the region's own code, so an allow-list of US and CA refuses every row below, the five US territories included, because each has a region code of its own. This is a map of the numbering plan, not a list of places to avoid: a service with customers in Puerto Rico names PR, and one with no customers in the other regions loses nothing by leaving them out.
| Region | Code | Area codes | US territory |
|---|---|---|---|
| American Samoa | AS | 684 | Yes |
| Anguilla | AI | 264 | No |
| Antigua & Barbuda | AG | 268 | No |
| Bahamas | BS | 242 | No |
| Barbados | BB | 246 | No |
| Bermuda | BM | 441 | No |
| British Virgin Islands | VG | 284 | No |
| Cayman Islands | KY | 345 | No |
| Dominica | DM | 767 | No |
| Dominican Republic | DO | 809, 829, 849 | No |
| Grenada | GD | 473 | No |
| Guam | GU | 671 | Yes |
| Jamaica | JM | 658, 876 | No |
| Montserrat | MS | 664 | No |
| Northern Mariana Islands | MP | 670 | Yes |
| Puerto Rico | PR | 787, 939 | Yes |
| Sint Maarten | SX | 721 | No |
| St. Kitts & Nevis | KN | 869 | No |
| St. Lucia | LC | 758 | No |
| St. Vincent & Grenadines | VC | 784 | No |
| Trinidad & Tobago | TT | 868 | No |
| Turks & Caicos Islands | TC | 649 | No |
| U.S. Virgin Islands | VI | 340 | Yes |
libphonenumber also answers DO for the valid toll-free range +1 800 14x, so an allow-list of US and CA refuses those numbers too.
Spaw reads NANPA's central-office code and thousands-block reports for the five territories as it does for the states; in NANPA's files of 26 September 2026 the territories' thousands-block rows are all Puerto Rico's. It holds no regulator table for the other eighteen, so their line type, and any carrier, come from the numbering plan alone and block_status answers null. The SMS-receiving lists, the unwanted-call complaint counts and your own delivery reports still apply to them. The data sources page dates every table the lookup reads.
What your own traffic can flag, and what it cannot
Three fields and two signals come from nothing but your own account's recent phone lookups, and they are the only part of an answer that can see a script at work.
range_burst, +40: your account looked up five or more distinct numbers in the same 1,000-number range within 60 minutes, this one included;range_seen_by_youanswers the count as{"distinct_numbers": 5, "window_minutes": 60}. The range is the E.164 number without its last three digits, a run of digits rather than a regulator's block, so the signal is a heuristic. It says a range was walked, not why: someone testing numbers against a form, or opening accounts in bulk, walks ranges too.numbers_per_client, +30: one client address tried five or more distinct numbers on your account within 60 minutes;client_velocityanswers the count in the same shape asrange_seen_by_you, and is null when no client address was sent; an IPv6 address counts by its /64. It stays quiet for an address Spaw's own IP data places on a mobile carrier or a corporate proxy, where many people share one address, and for a private or reserved address. It flags one script cycling numbers from one source, not an attack rotated through residential proxies.destination_new_for_you: true when your account has not looked up a valid number in that country in the last 30 days. It carries no weight and cannot be named inblock_signals, which answers422for it, and it is null until a week of the account's own lookups has been counted.
They are counted for single lookups (POST /api/v1/phone, the dashboard, the MCP verify-phone tool, the consistency check) and for browser lookups with a publishable key. Batch, bulk and monitor runs are never counted, and neither are the test numbers or the guest demo. A browser answer returns the three fields null, because it reaches your visitor, while the two signals still score there. No other account's lookups are read. What is kept is a keyed hash of each number and client address for the 60-minute window, and per country and hour, counts of lookups and of invalid answers for 30 days.
Three emails go to the account owner from the same counts:
| Alert | When | How often |
|---|---|---|
| Range burst | A range reaches five distinct numbers inside the window | Once per range per window, at most five a day |
| Destination spike | An hour with at least 20 lookups to one country and ten times that country's hourly average over the previous 14 days, or over the days counted so far when fewer; not sent until a week of the account's lookups has been counted | At most once per country a day |
| Enumeration | An hour with at least 50 lookups to one country, half or more of them invalid or in blocks nobody holds. Input that names no country, the international-network numbers included, counts as one destination of its own | At most once per country a day |
The destination alerts are checked every five minutes. The spike alert does not watch the no-country destination, so a surge of valid international-network numbers sends no email; an allow-list or international_network in block_signals is the control for them.
None of the eight test numbers can show these signals. Test numbers are never counted as your traffic, and none of them is an international-network number. What they can show is the country rule: with a stored allowed_countries of ["GB"], a secret-key lookup of +1 202 555 0100 answers ok_to_send: false with blocked_by: "country". The guest demo and the sandbox key answer under the defaults, so try the rule with a secret key.
After the send: delivery reports and verified_share
Pumped messages are delivered and never used; AWS lists "reduced deliverability rates due to the messages never being interacted with" among the challenges AIT poses, in the June 2024 post above. Report outcomes to POST /api/v1/phone/feedback, delivered from your SMS provider's receipts and verified when a code is entered, and the feedback summary answers verified_share over 90 days: of the numbers reported delivered or verified, the share reported verified. It is your one-time-code conversion, and a drop is worth reading beside the destination counts on the Usage page. It only works if you report verified. The summary's block list is ranked by failed deliveries and holds only blocks with at least one, so a pumped range whose messages all arrive may never appear in it.
What the policy is not
ok_to_send is a fraud and deliverability decision. It is not consent to message anyone and not a do-not-call scrub, and both stay your obligation. A country list is a business choice about where you send, not a verdict that a country's numbers are fraudulent. The own-traffic signals describe the pattern of your lookups, not a person: Spaw does no reverse lookup and names nobody behind a number.
What to do next
- Read the SMS policy section of the phone docs and the own-traffic section for the full contract.
- Change your policy with
PUT /api/v1/phone/policy; the SMS policy glossary term defines it in one page. - Read the international_network signal before you decide whether you send to satellite and network numbers.
- Check each region an allow-list names on phone facts by country.
- See the signals for rented and SMS-receiving numbers in stop OTP abuse with block data and disposable numbers.