# WhatsApp Number Scraper & Phone List Cleaner (`badr_xba/whatsapp-number-scraper`) Actor

Cleans phone lists as they really arrive (+212…, 0…, (212) 6…) into one international column with country, carrier, line type and time zone, duplicates merged and impossible numbers flagged. Optional WhatsApp registration check via your provider. 199 countries. Example: Morocco as default.

- **URL**: https://apify.com/badr\_xba/whatsapp-number-scraper.md
- **Developed by:** [Badr Bouargan](https://apify.com/badr_xba) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.50 / 1,000 number processeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## WhatsApp Number Validator – Bulk Checker & List Cleaner

![WhatsApp Number Validator](https://api.apify.com/v2/key-value-stores/paDGjcmD1ZMsRJLZS/records/phone-banner.png?signature=TVXxcun5HEYansNV9G25)

Check whether phone numbers are registered on **WhatsApp**, one at a time or in bulk, without sending a message and without alerting anyone. Every number is cleaned and validated first, so you never pay to check a number that cannot exist.

Built for sales teams qualifying leads, agencies cleaning lists before a campaign, and CRM teams deduplicating contact databases.

### How the WhatsApp check works — the part nobody explains

WhatsApp does not publish registration status anywhere. Tested, not assumed:

- `wa.me/212600000000` returns **the same 222 KB page** for a number that exists and one that does not.
- `api.whatsapp.com/send?phone=…` behaves identically.
- `web.whatsapp.com` answers `HTTP 400` without a signed-in session.
- WhatsApp's `robots.txt` states that *"collection of data on Facebook through automated means is prohibited unless you have express written permission"*.

So **no Actor reads WhatsApp status from a public page** — not this one, not any other. Every validator on the market queries a service that holds an authenticated connection; the well-known ones admit it at the bottom of their page, in one line.

This Actor is honest about it and puts you in control: you bring a validation provider, and the Actor does everything around it. Switch on **Check WhatsApp registration** and fill in five fields from your provider's documentation — address, key, method, the name of the number field, and where the yes/no sits in their answer. No provider is hard-coded: their endpoints change, and shipping one that cannot be tested would be a promise rather than a feature.

### Why the cleaning stage matters to your bill

Providers charge per number checked. A real list is 20–40 % unusable: numbers written three different ways, landlines, missing country codes, typos. Sending those to a paid API is money burnt.

So before anything is checked, this Actor:

- **normalises** every number to international form — `0612345678`, `(212) 612-345-678` and `+212612345678` become one number
- **removes duplicates** — that example was one contact, not three
- **drops what cannot exist** — wrong length for the country, prefixes no operator uses, repeated digits
- **flags landlines** so you can skip them before a messaging campaign
- **adds** country, carrier, line type and time zone, from Google's libphonenumber

![Real output](https://api.apify.com/v2/key-value-stores/paDGjcmD1ZMsRJLZS/records/phone-output.png?signature=1b12S98sA1BcnQBjVLCqo)

On the test list above: **17 numbers in, 14 out** — three were the same Moroccan mobile written three ways, and three could not exist. Only the 11 valid ones would have reached a provider.

That stage runs whether or not you have a provider, takes 3.9 seconds for 17 numbers, and costs $0.0001.

### What "valid" means, and what it does not

`+212600000000` comes back **valid**: it is a structurally correct Moroccan mobile on an Inwi prefix. Whether anyone answers it is a different question, and no format check can answer it.

| Question | Answered by |
| --- | --- |
| Could this number exist? | The cleaning stage, always, at almost no cost |
| Is there a WhatsApp account on it? | Your provider, on the numbers that survived cleaning |

### Step-by-step

1. Paste your list into **Phone numbers** — one per line, any spelling.
2. If some numbers are written locally (`0612345678`), pick the **country** they belong to.
3. Switch on **Check WhatsApp registration** and fill in your provider's five fields.
4. Optional: **Keep only mobile**, or restrict to certain **countries**, to shrink what gets checked.
5. Press **Start**, then export as CSV, Excel or JSON.

### Input example

```json
{
  "phoneNumbers": ["212612542341", "212612345678", "0612345678"],
  "defaultCountry": "MA",
  "keepOnlyMobile": true,
  "checkWhatsApp": true,
  "whatsappApiUrl": "https://api.your-provider.com/v1/check",
  "whatsappApiKey": "your key",
  "whatsappResultPath": "exists"
}
```

### Output example

One row per number, in the shape other WhatsApp validators use, so a file from this Actor can replace theirs:

```json
{
  "phone": "212612542341",
  "exists": true,
  "source": "live",
  "timestamp": "2026-09-23T12:36:30.000Z",
  "status": "success",
  "error": null,

  "input": "0612542341",
  "e164": "+212612542341",
  "valid": true,
  "country": "MA",
  "countryName": "Morocco",
  "numberType": "mobile",
  "isMobile": true,
  "carrier": "Orange",
  "timezones": ["Atlantic/Canary"],
  "international": "+212 6 12 54 23 41",
  "issues": []
}
```

`exists` and `source` are filled only when a provider is configured. Everything below them comes from the cleaning stage and is always there.

### Coverage

**199 countries**, from the United States to Tuvalu, each with its dial code — including **Morocco (+212), the United Arab Emirates (+971) and Saudi Arabia (+966)**. Any country code is recognised, whether or not it is in the dropdown; the list exists so you can say where locally-written numbers belong, and filter on country.

### Rate limits

Your provider sets the pace, not this Actor. Tell it how many requests per minute your plan allows and it keeps to that instead of discovering the limit by being refused. Numbers rejected during cleaning are never sent, so they never count against your quota.

### Frequently asked questions

**Does it message the numbers?**
No. Nothing is sent and no owner is notified. A serious provider queries a directory; it does not make the phone ring.

**Can I check numbers in bulk?**
Yes — pass an array in **Phone numbers**. There is no built-in ceiling; your provider's plan is the limit.

**What if I do not have a provider yet?**
The Actor still runs and cleans the whole list, with the `exists` column left empty. That alone removes duplicates and impossible numbers, which is what you would otherwise pay to discover.

**Which countries are supported?**
All of them. Include the country code with each number, or set the default country for locally-written ones.

**Why is a number I know is dead marked valid?**
Because it is well-formed. See the table above — format and service are different questions, and that is exactly what the WhatsApp check is for.

### Legal note

Phone numbers identify people. Under the GDPR and similar laws you need a lawful basis to process them, and separate consent before sending marketing messages — cleaning a list does not make it lawful to contact. Results stay in your own dataset on Apify, and nothing leaves the run unless you configure a provider yourself.

# Actor input Schema

## `checkWhatsApp` (type: `boolean`):

Tells you, per number, whether a WhatsApp account exists — without messaging anyone. WhatsApp publishes this nowhere: wa.me answers the same page for a number that exists and one that does not, and web.whatsapp.com needs a signed-in session. So this step calls a validation service you subscribe to, using your key, exactly like every other WhatsApp validator on the market. Fill in the five fields below with your provider documentation.

## `whatsappApiUrl` (type: `string`):

The endpoint your provider documents, for example https://api.example.com/v1/whatsapp/check

## `whatsappApiKey` (type: `string`):

Your API key. Stored encrypted by Apify and never written to the dataset or the log.

## `whatsappApiMethod` (type: `string`):

How your provider expects the request.

## `whatsappNumberField` (type: `string`):

What your provider calls the number: phone, number, msisdn…

## `whatsappResultPath` (type: `string`):

Path to the yes/no value in the provider answer. Use dots to go deeper: data.exists, result.0.is\_registered.

## `whatsappKeyHeader` (type: `string`):

Usually Authorization, sometimes X-API-Key.

## `whatsappKeyPrefix` (type: `string`):

Written in front of the key in the header. Empty for providers that want the bare key.

## `whatsappRequestsPerMinute` (type: `integer`):

The pace your plan allows. The Actor keeps to it instead of discovering the limit by being refused.

## `phoneNumber` (type: `string`):

One number to check, with its country code and no spaces: 212612345678. Handy for a quick test.

## `phoneNumbers` (type: `array`):

The list to clean. Any writing works — +212612345678, 212612345678, 0612345678, (212) 612-345-678 — and the Actor puts them all into one international form.

## `defaultCountry` (type: `string`):

Numbers written without a country code — 0612345678 — belong to this country. Leave empty if every number in your list already carries its code.

## `countries` (type: `array`):

Drops every number that does not belong to one of these countries. Cleaning first is what keeps the bill down: a malformed number is never sent to your provider. Leave empty to keep them all.

## `keepOnlyValid` (type: `boolean`):

Drops numbers that cannot exist — wrong length for their country, or a prefix no operator uses. Off, they are kept and flagged, so you can see what your list was carrying.

## `keepOnlyMobile` (type: `boolean`):

Drops landlines, toll-free and premium numbers. Useful before any messaging campaign. In the United States and Canada the two cannot be told apart, so those numbers are kept.

## `deduplicate` (type: `boolean`):

Two spellings of the same number — 0612345678 and +212612345678 — are the same line. Off, duplicates are kept and point at the first one through duplicateOf.

## `includeCarrier` (type: `boolean`):

The operator the number was issued to (Orange, Etisalat, STC…). Not available everywhere.

## `includeTimezone` (type: `boolean`):

The time zones the number belongs to — useful to call or message at a decent hour.

## `maxItems` (type: `integer`):

A ceiling for the run. 0 means no ceiling.

## Actor input object example

```json
{
  "checkWhatsApp": true,
  "whatsappApiMethod": "GET",
  "whatsappNumberField": "phone",
  "whatsappResultPath": "exists",
  "whatsappKeyHeader": "Authorization",
  "whatsappKeyPrefix": "Bearer ",
  "whatsappRequestsPerMinute": 20,
  "phoneNumbers": [
    "212612345678",
    "+971501234567",
    "0612345678",
    "+12125551234"
  ],
  "keepOnlyValid": false,
  "keepOnlyMobile": false,
  "deduplicate": true,
  "includeCarrier": true,
  "includeTimezone": true,
  "maxItems": 0
}
```

# Actor output Schema

## `overview` (type: `string`):

International form, country, line type and carrier for each number.

## `problems` (type: `string`):

Unparseable, impossible or wrongly written numbers, with the reason.

## `whatsapp` (type: `string`):

Registration results from your provider, when one is configured.

## `allFields` (type: `string`):

Every field, including national format, time zones and duplicates.

## `runStats` (type: `string`):

How many numbers were valid, mobile, duplicated or filtered out.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "checkWhatsApp": true,
    "phoneNumber": "",
    "phoneNumbers": [
        "212612345678",
        "+971501234567",
        "0612345678",
        "+12125551234"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("badr_xba/whatsapp-number-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "checkWhatsApp": True,
    "phoneNumber": "",
    "phoneNumbers": [
        "212612345678",
        "+971501234567",
        "0612345678",
        "+12125551234",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("badr_xba/whatsapp-number-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "checkWhatsApp": true,
  "phoneNumber": "",
  "phoneNumbers": [
    "212612345678",
    "+971501234567",
    "0612345678",
    "+12125551234"
  ]
}' |
apify call badr_xba/whatsapp-number-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,badr_xba/whatsapp-number-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/KtyURy6MCab9lbTBR/builds/c6o0NHQOG0ig67iZv/openapi.json
