What is SMS pumping?
SMS pumping, also called OTP toll fraud or artificially inflated traffic (AIT), is the abuse of any flow that sends a one-time code over SMS — signup, login, phone verification, or the resend-code step. The attacker scripts huge volumes of OTP requests, frequently to premium-rate or partner number ranges they share revenue on, so each verification SMS you pay to send turns into their payout while you absorb the messaging cost. The revenue-share mechanism behind it is sometimes called International Revenue Sharing Fraud (IRSF).How ShieldLabs surfaces it
The phone number, the SMS, and the carrier stay in your messaging stack. ShieldLabs resolves the session that asks for the code to a set of identifiers and grades how anonymous that session is. The anchor is the durable DeviceID — derived server-side from hundreds of stable browser components, so it survives cleared cookies, incognito, and IP rotation, while the cookie-scopedvisitor_id resets every request. That gives you four things a pumper cannot easily rotate away:
| Layer | What it answers | Where you read it | Latency |
|---|---|---|---|
| Identification | ”Is this the same device, even after cleared cookies, incognito, or a new IP?” | The durable device_id on the webhook / History API | About a second |
| Anonymity detection | ”How anonymous is the session requesting this OTP right now?” | The signals array on the webhook / History API | About a second |
| Risk Score | ”How risky is the visit overall, as one 0-100 number?” | risk_score on the webhook / History API | About a second |
| Patterns | ”Has this device or local IP already opened many accounts over time?” | Dashboard Patterns + export | Background (~10 min) |
ip_mismatch flag set. ShieldLabs tells you which device is asking and how masked it is; your code decides whether to send the SMS.
Stop SMS pumping
Identify the session at the exact step your app is about to send a verification SMS, then split the work cleanly: ShieldLabs returns the durable DeviceID, the anonymity signals, and the Risk Score; your code keeps the counter. Key a per-device send counter on the DeviceID (and a second one onlocal_ip.ip, the real local network address) so a fraudster who rotates public IPs and clears cookies still hits the same caps. Weigh a masked session more heavily — a Datacenter or VPN session asking for codes in bulk is the typical pumping shape, so it earns a tighter cap or a CAPTCHA before the send. A throttle keyed only on the public IP, a cookie, or a session resets every request and never builds; keying it on the DeviceID is what makes the rotation stop working. Apply the same per-device counter to the resend-code button too — re-requesting a code is the cheapest way for one session to run up the bill, so each resend should increment the cap, not reset it. The steps below wire it up.
Build it
Create a ShieldLabs account and get your keys
Sign up for free and get 5,000 identifications, or log in if you already have an account. Register the domain you want to identify visitors on, then open the Keys page. Use the Public Key to initialize the snippet in the browser, and keep your server-side credentials on your backend: the Private API Key authenticates the History API, and each webhook endpoint has its own
whsec_… signing secret. See Keys.Identify the session at the OTP step
Add the snippet to your app and identify the session right where you collect the phone number or trigger the code. Call
checkAnonymous before an account exists (a signup or phone-verify form), or checkAuthenticatedUser with the account’s hashed id (UserHID) when the user is logged in. Pass a hash, never a raw email, phone number, or user id.verify.html
Read the device and signals before you send
Before your backend calls the SMS provider, pull the scored result for that
RequestID from your webhook cache (the shared waitForScore helper), or fall back to the History API. Read the durable device_id, the score, and the local IP — these are what your counters key on.api/request-otp.js
Weigh masked sessions with detection_flags
When the policy depends on a specific tell rather than just the band, read the boolean
detection_flags on the webhook: datacenter_ip, vpn, proxy, tor, anti_detect_browser, abuser, ip_mismatch, and more. These are stable booleans built for branching, so “datacenter plus a hot device counter, require a CAPTCHA” reads cleanly. A masked session can also show detection_flags.ip_mismatch: true — the public public_ip and the real network IP (local_ip) resolve to different countries. Treat it as corroborating evidence, not a trigger on its own: it is informational, does not change the Risk Score, and can be benign (mobile networks often route over different paths). Use the signals array ({ name, weight }) only for the explainable breakdown.Honest framing: a legitimate user on a corporate VPN sometimes verifies a phone too. Anonymity tightens the cap or adds a CAPTCHA before the send, it does not justify refusing the code outright on its own. Reserve a hard deny for the combination of anonymous infrastructure, a device or local IP already over your send limit, and the fan-out pattern below.
Watchlist the fan-out with Patterns
Pumping for signup OTPs usually means one device opening many accounts. Patterns link sessions over time and grade each entity Suspicious then Dangerous as that count climbs in a rolling window. Below the Suspicious threshold an entity is the unflagged baseline, which is never recorded.
Pull the flagged entities from the dashboard Patterns tab (CSV or JSON) and feed the Dangerous DeviceIDs and local IPs into your send-cap logic as a watchlist. You can also reconstruct a device’s fan-out live from the History API.
Many Accounts on One Device
Many Accounts on One Device
One device opening or touching many different accounts. Keyed on the durable DeviceID, so it holds even as the pumper rotates public IPs and clears cookies between requests — the same machine driving a flood of signup OTPs.
Many Accounts on One Local IP
Many Accounts on One Local IP
Many accounts reached through the same local IP. Catches a single machine or NAT fanning out across signups behind a rotating public IP.
How many accounts has this device opened?
Tighten the cap on a known fan-out device
Account History reads on
account.shieldlabs.ai, the webhook stream, and the dashboard export are free; the alternate Management history path bills 1 request per returned row (an empty result still bills 1). Lean on the free sources for routine watchlisting and reserve a billed read for a device you are about to act on.Tune to your product
A consumer signup flow sends more first-time verifications than a niche B2B tool. Start in logging-only mode, watch how many OTP requests your real sessions make per device and per local IP, then set the send caps and the band that match your traffic before you turn on enforcement. A device that requests many codes but rarely completes a verification is a classic pumping shape, so track completion against the
device_id and feed a high request-to-completion ratio into the same watchlist.Test it
You do not need a real attack to confirm the cap holds. Open your phone-verify page and complete an identification, noting thedevice_id on the webhook. Now repeat the ways that should not reset your counter: a fresh incognito window, the same browser after clearing cookies and storage, and (where you can) a second public IP. Because the DeviceID is server-derived rather than stored, the same device_id comes back each time, so your per-device send counter keeps climbing across all of those attempts instead of starting over — meaning one machine cannot reset its way into a flood of paid SMS. Switch to a genuinely different physical device and the device_id changes, confirming the key is tied to the device and not to anything a pumper can clear.
Recommended starting policy
A guide, not a rule. The right send caps depend entirely on your verification flow. Layer the conditions: friction should rise as more of them stack.| Condition | Suggested action at the send step |
|---|---|
| Clean session (Score under 30), under your send cap | Send the code |
Score in the High band (60+), or anonymous infra (datacenter_ip / proxy / vpn) | Require a CAPTCHA before sending |
| DeviceID or local IP over your send cap (across rotated IPs) | Rate-limit (HTTP 429), do not send |
| Device or local IP flagged “Many Accounts on One…” | Treat as a pumping source: deny the send, then review |
Next: Acting on the Risk Score
The full decision playbook, including how to combine the device, the anonymity signals, and the per-session Risk Score into one verdict.