Skip to content

Stop OTP abuse with block data and disposable numbers

published · September 5, 2026 · updated · September 27, 2026

Which numbers soak up one-time codes and free trials, the four open signals that catch them, and a signup policy that does not punish real people.

A one-time code is only as good as the number it goes to. If anyone can read the code, the number is not proof of a person, and every product that gates a free trial, a coupon or a second account behind SMS has met the same numbers doing it: rented virtual numbers, numbers published on websites that show incoming texts to the world, and the digit patterns people type when they do not want to be reached. This guide walks through the four open signals Spaw reads for every lookup, what each one proves, and how to gate a signup with them without blocking the people you want.

Where the numbers come from

Most abuse does not come from a handset. It comes from a number that was never meant to be one person's phone:

  • Virtual numbers. Wholesalers such as Bandwidth, Onvoy and Telnyx in the United States, Iristel and Fibernetics in Canada, and a handful of European carriers hold blocks whose numbers are resold by the thousand to apps, call centres and SMS-receiving services. A number in one of those blocks can be rented for minutes.
  • Numbers published on SMS-receiving websites. These sites show every incoming message on a public page. Anyone can pick a number, use it for a signup, and read the code without owning anything.
  • Fictional ranges. The North American 555-01XX block and Ofcom's drama ranges exist so that films and television never dial a real person (https://www.ofcom.org.uk/phones-and-broadband/phone-numbers/numbers-for-drama, accessed 5 September 2026). Nobody will ever answer them, which makes them a favourite for people filling in a required field.
  • Digit patterns. 555-5555 and 123-4567 are what people type when the form insists on a number and they do not intend to give one.

None of these is fraud in itself. All of them mean the code will not reach a person's own phone.

The four signals and what each proves

Spaw reads all four from data it holds locally, so every signal is in the same answer at the same single credit.

Signal Field Where it comes from
Virtual-number block is_virtual: true, line_type: "voip" with line_type_source: "block_allocation" The regulator's allocation table names the block holder (in a pooled US code, the holder of the number's own block of a thousand); a wholesaler's name settles it.
Published on an SMS-receiving website is_disposable: true, disposable_seen_at Two open lists of numbers scraped from those sites, synced daily, with the date each number was last seen; a number that leaves the lists keeps that date and ages.
Fictional range is_fictional: true The published fiction and drama ranges, bundled with the service.
Digit pattern digit_pattern in risk_signals The last seven digits repeat one digit or run straight up or down.

The disposable list deserves one honest note. The open list Spaw syncs (https://github.com/iP1SMS/disposable-phone-numbers, accessed 5 September 2026) records when each number was last seen on a site, and most entries are old: of the 126,076 numbers on the copy synced on 5 September 2026, 109,509 were last seen more than a year earlier. A number a site showed two years ago may have been returned to the carrier and reassigned to a real subscriber since. That is why a listing the lists still carry scores 70 points while it was seen within the last year and 40 after that, and why the date is in the answer for you to weigh yourself. Since 24 September 2026 a second list is merged in (https://github.com/castle/disposable-phone-numbers, accessed 24 September 2026), a rolling list of about a thousand numbers the sites are showing now, dated by the day of the sync; a number that drops off every list is kept at its last-seen date for two years instead of vanishing, and ages faster than one still listed: it scores 70 for 90 days after it was last seen, then 40, because a number the sites no longer show may already have been handed back.

The sites rent numbers in runs and the lists lag the sites, so the neighbours of listed numbers carry a signal of their own. A number that is not listed itself, in a 1,000-number range holding ten or more listings that still score 70, scores disposable_range, 30 points: medium on its own, never high, and never added to a listing's own weight.

The virtual-block signal has its own caveat. A wholesaler's block backs plenty of legitimate use: a business phone system, a customer's second line, a number from an app that people use as their everyday number. is_virtual means the number was allocated to a wholesaler, not that the person behind it is up to something.

Read the score, then decide

The risk score is a sum of published weights over these flags, capped at 100, and risk_signals lists every term so you can recompute it. The weights that matter here: fictional 80, disposable 70, disposable_stale 40, virtual 40, voip 30, digit_pattern 30. A number in a wholesaler block that an SMS-receiving site shows now scores 110 before the cap, so 100 and high. A number in a wholesaler block and nothing else scores 40 and medium. A plain mobile in a carrier's block scores 0.

Spaw does not call any of this fraud. The score measures how unlikely a number is to reach one person's own handset, which is exactly the question a one-time code asks.

A policy that does not punish real people

Block only on the signals that prove the code cannot land, and step up instead of blocking on the signals that merely lower the odds:

  1. Reject invalid numbers outright. valid: false with any reason, including unassigned_block, costs nothing and never reaches anyone. Show the reason in plain words: a missing country, a number that is too short, a block nobody holds.
  2. Reject fictional numbers and fresh disposable listings. is_fictional and a fresh listing, the disposable signal, are the two signals that mean "anyone or no one can read this code". Ask for another number.
  3. Step up on medium. A wholesaler block, a stale listing or a digit pattern is a reason to send the code and also require something else, such as an email confirmation or a delay before the trial unlocks, not a reason to refuse the person. Wholesaler blocks are full of real customers.
  4. Never block on fixed_line_or_mobile. In North America that is the plan's honest answer for most real mobiles. Blocking it blocks your customers.
  5. Keep the answer. Store e164, the flags and risk_score next to the account so a later review can see why a signup was allowed.

Run the policy from the browser and the server both. The form helper marks the field as the person types, with data-spaw-phone, and the server checks the same number again on submit, because a page can be edited. Both calls answer from the same tables, and a repeat of the same number within seven days is free.

On the server call, send client_ip, the address the signup came from, before each code goes out. It is never looked up or billed, only counted: client_velocity answers how many distinct numbers your account has looked up from that address in the last hour, and five or more add numbers_per_client (30). range_seen_by_you does the same for one 1,000-number range, and five or more add range_burst (40): one script cycling numbers through a form, or walking a range where someone is paid for every message sent there, looks clean one number at a time and obvious as a count. Both signals can be named in the SMS policy's blocked signals so the answer refuses the code outright; the own-traffic section of the phone docs explains the windows and what is kept.

Report what happened next, too. Send your delivery receipts to POST /api/v1/phone/feedback, and report verified when the code is entered: the summary's verified_share is then your conversion, and a block that is delivered but never verified is where pumped traffic shows first.

Test it before you ship it

The eight test numbers answer fixed results and never cost a credit: +1 202 555 0102 is a virtual number, 0103 is a fresh disposable listing, 0113 sits in a 1,000-number range crowded with SMS-receiving numbers (disposable_range 30), 0105 is fictional, 0104 sits in an unassigned block and 0100 is a clean mobile. Wire them into your integration tests so the policy above is exercised on every deploy, without depending on what the open lists say that week.

What to do next

Related

markdown version: /guides/stop-otp-abuse-with-block-data-and-disposable-numbers.md

Verify addresses the same way

The Spaw API runs every check described here on each lookup, with an SMTP handshake that never sends mail. 10 free lookups a month, no card required.

Get your API key

More guides