# WhatsApp Number Validator (`automation-lab/whatsapp-number-validator`) Actor

Validate phone numbers against public WhatsApp account pages and return conservative resolution evidence plus public business metadata.

- **URL**: https://apify.com/automation-lab/whatsapp-number-validator.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## WhatsApp Number Validator

Validate international phone numbers against anonymous public WhatsApp send pages.
Normalize messy CRM values, identify numbers with publicly exposed WhatsApp account metadata, and keep uncertain or blocked checks separate from invalid input.

This Actor uses lightweight HTTP requests—no WhatsApp login, browser, or private API token is required.
It returns conservative evidence instead of turning network failures or generic pages into false negatives.

### What does WhatsApp Number Validator do?

For every unique phone number, the Actor:

1. preserves the original input;
2. strips common formatting and normalizes it to digits-only international form;
3. opens the public `wa.me` route and follows its WhatsApp redirect;
4. reads public Open Graph account metadata;
5. classifies the result as `registered`, `unresolved`, `blocked`, or `invalid_input`;
6. saves HTTP and canonical URL evidence to the default dataset.

A `registered` result means WhatsApp exposed named public account metadata.
An `unresolved` result means the public send page remained generic.
It does **not** prove that the number has no private or personal WhatsApp account.

### Who is it for?

#### CRM and revenue operations teams

Normalize imported phone columns and separate publicly resolved WhatsApp Business accounts from records requiring another verification step.

#### Lead enrichment providers

Add public account name, account type, profile image, and canonical WhatsApp URL to phone-number records.

#### Messaging agencies

Clean duplicate and malformed values before campaign preparation.
This Actor does not send messages or grant permission to contact anyone.

#### Customer support operations

Revalidate support contact lists on a schedule and route `blocked` results for retry instead of treating them as absent accounts.

#### Developers and data engineers

Use direct arrays, an Apify dataset, or a key-value store as the source, then export typed JSON, CSV, Excel, or Parquet output.

### Why use conservative public resolution?

Many validators collapse every unsuccessful request into `false`.
That is unsafe when an upstream service rate-limits requests, serves a challenge, or keeps a personal account private.

This Actor instead provides:

- `isRegistered: true` only when public evidence supports it;
- `isRegistered: null` when registration cannot be proved;
- a separate `blocked` status for network and challenge failures;
- a separate `invalid_input` status before any HTTP request;
- no charge for blocked or invalid inputs;
- duplicate normalized numbers checked only once per run.

### What data can I extract?

| Field | Type | Meaning |
| --- | --- | --- |
| `input` | string or number | Original supplied value |
| `normalizedNumber` | string or null | Digits-only international number |
| `status` | string | `registered`, `unresolved`, `blocked`, or `invalid_input` |
| `isRegistered` | boolean or null | True only for a publicly resolved account |
| `resolved` | boolean | Whether named public metadata was resolved |
| `publicName` | string or null | Public account or business name |
| `accountType` | string or null | Public classification such as `Business Account` |
| `profileImageUrl` | string or null | Public profile image from page metadata |
| `canonicalUrl` | string or null | Final public WhatsApp send URL |
| `httpStatus` | number or null | Final response status |
| `attempts` | number | HTTP attempts used |
| `error` | string or null | Diagnostic reason for blocked or invalid results |
| `checkedAt` | ISO timestamp | Time the record was produced |

Every input produces a typed record unless it is a duplicate after normalization.
Only completed `registered` and `unresolved` checks emit a billable validation event.

### How do statuses work?

#### `registered`

The public page exposed a non-generic name, such as a business identity.
`isRegistered` is `true` and available metadata is returned.

#### `unresolved`

WhatsApp returned a recognizable but generic “Share on WhatsApp” page.
`isRegistered` is `null`, not `false`, because public metadata alone cannot prove absence.

#### `blocked`

The route returned a challenge, rate limit, unexpected HTTP response, timeout, or unrecognizable page.
Inspect `httpStatus`, `attempts`, and `error` before retrying later.

#### `invalid_input`

The value cannot be normalized to an international number with 8–15 digits.
No upstream request is sent and no validation event is charged.

### Getting started

1. Open the Actor input page in Apify Console.
2. Add numbers under **Phone numbers**.
3. Include the country calling code for every number.
4. Keep concurrency at the default of 3 for the first run.
5. Click **Start**.
6. Open the **Dataset** tab to inspect status and evidence.
7. Export the data or connect the dataset to your workflow.

A small working input is:

```json
{
  "phoneNumbers": [
    "+14155238886",
    "+12025550123"
  ],
  "maxItems": 2,
  "maxConcurrency": 2
}
```

### Input parameters

#### `phoneNumbers`

An array of strings (or numeric values through the API).
Formatting characters are removed, so `+1 (415) 523-8886` becomes `14155238886`.
The Actor does not infer a missing country calling code.

#### `datasetId` and `datasetField`

Set `datasetId` to load existing dataset items.
The Actor reads each number from `datasetField`, which defaults to `phoneNumber`.
You can combine dataset values with direct `phoneNumbers`.

```json
{
  "datasetId": "SOURCE_DATASET_ID",
  "datasetField": "contactPhone",
  "maxItems": 5000
}
```

#### `keyValueStoreId` and `keyValueStoreKey`

Set `keyValueStoreId` to load a JSON array from a key-value store.
The default key is `PHONE_NUMBERS`.
A non-array value fails the run instead of silently returning no data.

```json
{
  "keyValueStoreId": "SOURCE_STORE_ID",
  "keyValueStoreKey": "PHONE_NUMBERS"
}
```

#### `maxItems`

Maximum unique normalized inputs processed after deduplication.
Allowed range: 1–100,000.
The default is 1,000.

#### `maxConcurrency`

Number of parallel HTTP requests.
Allowed range: 1–10; default: 3.
Lower it when repeated checks encounter upstream rate limits.

### Output example

A public business result currently looks like:

```json
{
  "input": "+14155238886",
  "normalizedNumber": "14155238886",
  "status": "registered",
  "isRegistered": true,
  "resolved": true,
  "publicName": "Twilio",
  "accountType": "Business Account",
  "profileImageUrl": "https://pps.whatsapp.net/...",
  "canonicalUrl": "https://api.whatsapp.com/send/?phone=14155238886&text&type=phone_number&app_absent=0",
  "httpStatus": 200,
  "attempts": 1,
  "error": null,
  "checkedAt": "2026-01-15T12:00:00.000Z"
}
```

A generic public page has `status: "unresolved"`, `resolved: false`, and `isRegistered: null`.

### How much does it cost to validate WhatsApp numbers?

Pricing has two events:

- **Start:** $0.005 once per run.
- **Completed validation:** tiered from $0.000041071 at FREE tier to $0.00001 at DIAMOND tier.

Blocked, invalid, and duplicate inputs do not emit a completed-validation event.
Apify platform usage may be billed according to your plan.

At the FREE event tier, approximate Actor charges are:

| Completed checks | Start | Validation events | Total |
| ---: | ---: | ---: | ---: |
| 10 | $0.005 | $0.000411 | $0.005411 |
| 1,000 | $0.005 | $0.041071 | $0.046071 |
| 10,000 | $0.005 | $0.41071 | $0.41571 |

Higher subscription tiers use the lower prices declared in the Actor pricing table.
Actual completed-event count can be lower than input count because duplicates, invalid inputs, and blocked requests are not charged.

### Bulk CRM cleanup workflow

1. Export contacts to an Apify dataset with a phone field.
2. Run this Actor with `datasetId` and the matching `datasetField`.
3. Join results back on `normalizedNumber`.
4. Send `registered` records to public business enrichment.
5. Route `unresolved` records to a secondary consent-aware verification process.
6. Retry `blocked` records later with lower concurrency.
7. Correct or remove `invalid_input` records.

This preserves uncertainty and avoids discarding valid contacts because of temporary delivery failures.

### Scheduling and integrations

Use Apify schedules for weekly or monthly revalidation.
Connect the default dataset to:

- Google Sheets for review queues;
- Make or Zapier for CRM updates;
- webhooks for run-completion notifications;
- Python or JavaScript ETL jobs;
- BigQuery, Snowflake, or another warehouse through an integration.

The Actor only reads public metadata and does not send WhatsApp messages.

### Use the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~whatsapp-number-validator/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumbers": ["+14155238886", "+12025550123"],
    "maxItems": 2,
    "maxConcurrency": 2
  }'
```

Wait for the run or use the synchronous dataset endpoint when the batch fits the endpoint timeout.
Never embed an Apify token in client-side code or a public repository.

### Use the Apify API with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/whatsapp-number-validator').call({
    phoneNumbers: ['+14155238886', '+12025550123'],
    maxItems: 2,
    maxConcurrency: 2,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Use the Apify API with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/whatsapp-number-validator").call(run_input={
    "phoneNumbers": ["+14155238886", "+12025550123"],
    "maxItems": 2,
    "maxConcurrency": 2,
})

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/whatsapp-number-validator"
```

#### Claude Desktop, Cursor, and VS Code setup

Claude Desktop, Cursor, and VS Code can use this equivalent HTTP MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/whatsapp-number-validator"
    }
  }
}
```

Example prompts:

- “Validate these international numbers and show only publicly resolved business accounts.”
- “Run the WhatsApp validator on the `phone` field in my source dataset.”
- “Group this run’s results by status and list blocked checks for retry.”

### Accuracy and limitations

- The Actor resolves public metadata, not WhatsApp’s private account database.
- `unresolved` is intentionally not equivalent to “not registered.”
- Personal or privacy-restricted accounts may expose no public name.
- WhatsApp can change HTML, metadata, redirects, or rate limits without notice.
- The Actor uses direct HTTP and has no automatic proxy or browser fallback in v1.
- Numbers must include an international country calling code.
- Results describe the public page at `checkedAt`; they are not permanent facts.
- Display names and profile images are controlled by the account owner.

For high-stakes identity, compliance, fraud, or deliverability decisions, combine this public signal with an authorized primary source.

### Responsible use and legality

Only process phone numbers you are authorized to handle.
Follow privacy, marketing, anti-spam, telecommunication, and data-protection laws that apply to your organization and recipients.

A public WhatsApp page does not provide consent to message, profile, or market to a person.
Do not use the Actor to harass users, evade platform controls, build sensitive-person dossiers, or make protected-class decisions.
Review WhatsApp’s current terms and your lawful basis before production use.
Honor deletion, access, suppression, and opt-out requests in downstream systems.

### Performance tips

- Start with concurrency 3 rather than immediately using 10.
- Deduplicate in one Actor run to reuse normalized-number checks.
- Use `maxItems` to bound trial and scheduled runs.
- Store numbers in international format before repeated validation.
- Retry `blocked` records later; do not reinterpret them as unresolved.
- Split very large workflows into auditable batches when downstream joins require stable snapshots.

### Troubleshooting

#### Why did an active personal account return `unresolved`?

The public send page did not expose a named identity.
The Actor cannot safely infer registration from private account state, so it returns `isRegistered: null`.

#### Why are results `blocked`?

Inspect `httpStatus`, `attempts`, and `error`.
Reduce `maxConcurrency` and retry later if WhatsApp is rate-limiting public requests.
A blocked record is not charged as a completed validation.

#### Why was my number marked `invalid_input`?

Include 8–15 digits and the country calling code.
Formatting punctuation is supported, but country inference and local-only numbers are not.

#### Why are there fewer outputs than inputs?

Equivalent formatted values normalize to the same digits and are checked once.
`+1 (415) 523-8886` and `0014155238886` are duplicates in the same run.

#### Can I provide CSV directly?

Import the CSV into an Apify dataset, then set `datasetId` and `datasetField`.
Alternatively, transform the column into the `phoneNumbers` input array.

### Related Automation Lab Actors

- [Phone Number Validator API](https://apify.com/automation-lab/phone-number-validator-api) for general number-format and carrier-oriented validation when WhatsApp-specific public resolution is not required.
- [WhatsApp Groups Scraper](https://apify.com/automation-lab/whatsapp-groups-scraper) for public group discovery workflows rather than individual phone-number checks.

Choose this Actor when the job is specifically to normalize a list and resolve public WhatsApp account metadata.

### FAQ

#### Does this Actor require a WhatsApp account?

No. It uses anonymous public send pages.

#### Does it send a message?

No. It only requests public page metadata.

#### Does `registered` guarantee that a number can receive my message?

No. It proves only that named public account metadata was visible at check time.
Delivery, consent, blocks, regional restrictions, and account changes are separate concerns.

#### Does `unresolved` mean the number is not on WhatsApp?

No. It means the anonymous public page did not expose enough evidence to resolve a named account.

#### Are duplicate checks charged twice?

No. Values that normalize to the same number are processed once per run.

#### Are invalid and blocked checks charged?

They do not emit the completed-validation event.
The one-time run start event still applies.

#### Can I increase concurrency?

Yes, up to 10, but higher concurrency can increase blocking without improving useful throughput.

#### Can I use the results for unsolicited outreach?

Public account metadata is not consent.
You are responsible for lawful contact, suppression lists, opt-outs, and platform rules.

# Actor input Schema

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

International phone numbers in E.164 or formatted raw form. Country inference is not performed; include the country calling code.

## `datasetId` (type: `string`):

Optional Apify dataset ID containing phone numbers. Values are read from datasetField.

## `datasetField` (type: `string`):

Field to read from each source dataset item.

## `keyValueStoreId` (type: `string`):

Optional Apify key-value store ID containing an array of phone numbers.

## `keyValueStoreKey` (type: `string`):

Record key whose JSON value is an array of phone numbers.

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

Maximum number of unique normalized inputs to process.

## `maxConcurrency` (type: `integer`):

Parallel WhatsApp requests. Keep this conservative to reduce rate limiting.

## Actor input object example

```json
{
  "phoneNumbers": [
    "+14155238886",
    "+12025550123"
  ],
  "datasetField": "phoneNumber",
  "keyValueStoreKey": "PHONE_NUMBERS",
  "maxItems": 10,
  "maxConcurrency": 3
}
```

# Actor output Schema

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

Default dataset containing one typed result per unique normalized input.

# 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 = {
    "phoneNumbers": [
        "+14155238886",
        "+12025550123"
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/whatsapp-number-validator").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 = {
    "phoneNumbers": [
        "+14155238886",
        "+12025550123",
    ],
    "maxItems": 10,
}

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

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

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

```

## CLI example

```bash
echo '{
  "phoneNumbers": [
    "+14155238886",
    "+12025550123"
  ],
  "maxItems": 10
}' |
apify call automation-lab/whatsapp-number-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/whatsapp-number-validator",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3dkCWaobeIfkHNDog/builds/EOJSJ6bbtlSQMnwiW/openapi.json
