Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

How to Check If a WhatsApp Number Is Active — Without Sending It a Message

Every team that sends WhatsApp messages at scale eventually asks the same question: can I tell whether this number is actually on WhatsApp before I try to message it? Sending a probe message is the obvious answer and th

Every team that sends WhatsApp messages at scale eventually asks the same question: can I tell whether this number is actually on WhatsApp before I try to message it?

Sending a probe message is the obvious answer and the worst one. It costs money, it can hit a stranger who inherited a recycled number, and enough of those will drag your sender quality rating down until Meta cuts your messaging limits. This post walks through what you can learn about a number without contacting it, where each signal stops being useful, and how to wire it into a list-cleaning job.

The cost of not knowing

Phone lists decay fast. Numbers get disconnected, SIMs churn, and in prepaid-heavy markets a number you collected eighteen months ago may belong to a different person today. A realistic decay figure for an e-commerce customer list is 20–30% bad or unreachable entries.

Run the math on a 100,000-number list with 25% decay:

  • Direct send costs. WhatsApp template messages are billed per delivered message, and SMS fallback (which many stacks trigger automatically when WhatsApp delivery fails) is billed per segment whether the number is alive or not. At $0.01–0.05 per segment, one campaign can burn $250–$1,250 on numbers that were never reachable.
  • Wrong-person costs. Recycled numbers that are on WhatsApp receive your message — and strangers block and report. Blocks and reports feed your quality rating, and a downgraded rating means lower throughput tiers for all your traffic, not just the bad rows.
  • Operational costs. Every failed or misdirected conversation lands in a CRM as a dead lead that sales keeps following up on.

A pre-flight check that costs fractions of a cent per number pays for itself if it removes even a few percent of the dead weight. The question is which check.

Signal 1: Line status (HLR-style lookups)

The oldest tool in this space is the HLR lookup. The Home Location Register is the carrier-side database of subscribers, and lookup services query it (directly or through aggregators) to answer questions about the line, not the apps on it.

An HLR/line-status check can typically tell you:

  • Whether the number is allocated and currently active on a mobile network
  • The current network (MCC/MNC) and whether the number has been ported
  • In some implementations, a coarse reachability signal for the device

What it cannot tell you is the thing you actually care about: whether that line has a WhatsApp account. A number can be a perfectly healthy, active SIM in a drawer, owned by someone who has never installed WhatsApp. Conversely the mapping between "line active" and "app registered" is loose enough in both directions that HLR alone is a weak predictor of messageability.

Use line status for what it's good at: killing clearly dead numbers cheaply, and detecting porting/country mismatches. Don't treat "active line" as "reachable on WhatsApp."

Signal 2: Registration status

Registration is a different layer. WhatsApp accounts are keyed by phone number in the service's own account registry, so "is this number registered on WhatsApp" is a question about the app's database, not the carrier's. That's why it needs a different kind of check than an HLR query — and why the result is more actionable: it maps almost one-to-one to "a message to this number can be delivered to a WhatsApp client."

A registration check returns a discrete status per number rather than a score. Used well:

  • wa_registered → safe to include in WhatsApp sends
  • dead → line is disconnected; drop it everywhere, including SMS
  • inactive → line exists but shows no recent app activity; segment separately, don't lead with it
  • unknown → couldn't be determined; fall back to your default policy

Two honest caveats. First, registration state is a snapshot — a number checked last month can churn this month, so re-validate lists you keep for a long time. Second, neither signal verifies identity: a registered number tells you nothing about whether it still belongs to the customer who gave it to you. Recycled-number risk shrinks with list hygiene, it doesn't disappear.

A note on scope

Everything above assumes you're verifying your own first-party data — numbers your users gave you at checkout or signup, with consent to be contacted. That's list hygiene. Running registration checks against scraped or purchased lists to build outreach targets is a different activity, it's what gets senders banned and providers shut down, and it's out of scope for this approach entirely.

Trying it: one number, one curl

TrueReach exposes both checks through a single endpoint. A real-time lookup looks like this:

curl -X POST https://truereach.one/v1/verify \
  -H "Authorization: Bearer <redacted>" \
  -H "Content-Type: application/json" \
  -d '{"phone": "5511987654321"}'
{
  "phone": "5511987654321",
  "status": "active",
  "checked_at": "2026-10-21T02:22:38Z"
}

The response is deliberately boring: the number, a status, and a timestamp. Boring is what you want from infrastructure you call in a loop.

Batch: cleaning a list before a campaign

Real-time calls are for ones and twos. For a list, the pattern is upload → poll → download the same rows back with a status column attached. The shape of the job:

import csv, time, requests

API = "https://truereach.one"
H = {"Authorization": "Bearer YOUR_API_KEY"}

# 1. Upload the list (CSV with a phone column)
with open("customers.csv", "rb") as f:
    task = requests.post(f"{API}/v1/verify/batch",
                         headers=H, files={"file": f}).json()

# 2. Poll until done
while True:
    t = requests.get(f"{API}/v1/verify/tasks/{task['task_id']}", headers=H).json()
    if t["status"] == "done":
        break
    time.sleep(5)

# 3. Download results and keep only sendable rows
rows = requests.get(f"{API}/v1/verify/tasks/{task['task_id']}/download",
                    headers=H).text.splitlines()
reader = csv.DictReader(rows)
sendable = [r for r in reader if r["verification_status"] == "wa_registered"]

print(f"{len(sendable)} numbers cleared to send")

In practice you'd persist the statuses back into your CRM rather than re-checking the whole list every campaign — check once at intake, re-check on a decay schedule.

Which mode when

Use real-time single checks when:

  • A number is arriving right now — checkout, signup, lead form — and you want to validate before storing it or triggering an OTP
  • Volume is low (hundreds a day) and latency matters more than unit cost
  • You're gating something downstream: no point sending a confirmation flow to a dead line

Use batch when:

  • You're about to run a campaign against an existing list (this is the main event)
  • You're doing periodic hygiene on a CRM that's accumulated months of decay
  • Volume is in the thousands-plus, where per-call overhead dominates

Pricing follows the same split: batch checks run $0.002 per number, real-time singles $0.0025 — cheap enough that the decision should be driven by workflow, not cost.

Closing

"Is this number on WhatsApp" sounds like one question but it's two — one about the carrier's line, one about the app's registry — and conflating them is how teams end up paying to message drawer SIMs. Check line status to kill dead numbers, check registration status to decide who gets a message, and do both before you send rather than learning from your block rate after.

If you want to run the checks above against your own list, TrueReach gives new accounts 2,000 free checks, and the full API surface is in the API docs.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.