# WhatsApp Profile Scraper - Picture, About, Business Data (`vero-api/whatsapp-scraper`) Actor

Extract publicly available contact and business profile information from WhatsApp and WhatsApp Business. Retrieve profile details such as display name, phone number, business information, profile photo, status for automation, CRM enrichment, lead qualification, and business intelligence workflows.

- **URL**: https://apify.com/vero-api/whatsapp-scraper.md
- **Developed by:** [VeroAPIs](https://apify.com/vero-api) (community)
- **Categories:** Social media, Lead generation, Automation
- **Features:** Limited permissions
- **Stats:** 439 total users, 106 monthly users, 98.7% runs succeeded, 5 bookmarks
- **User rating**: 4.13 out of 5 stars

## Pricing

from $5.00 / 1,000 checked numbers

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 Profile Scraper & Number Validator

**WhatsApp Scraper** is a bulk **WhatsApp data extraction** tool that turns a list of phone numbers into rich, structured profile data. For every number it verifies whether the account exists on **WhatsApp** and then **scrapes the public WhatsApp profile** — **profile picture**, **about/status text**, **personal-vs-business** account type, and, for **WhatsApp Business** accounts, the full business profile: **business name, category, email, website, address, and opening hours**. Feed it phone numbers and get back clean data you can download as **JSON, CSV, Excel, HTML, or XML**.

It's both a **WhatsApp profile scraper** and a **WhatsApp number validator**: use it to **scrape WhatsApp Business contact details** (email, website, address) for lead generation, **enrich CRM records** with profile data, and **check if a number is on WhatsApp** to clean phone lists before a campaign. It runs in the cloud and needs no coding.

> **Paid Apify plans only.** This Actor runs on paid plans only. On a free plan the run ends immediately with a "paid plan required" message and you are charged nothing. [Upgrade your plan](https://apify.com/pricing) to use it.

### What does the WhatsApp Scraper do?

For each phone number, the Actor:

1. **Checks the number format first** — only numbers that read as real international numbers are looked up. Entries that can't be read that way, such as `12345` or `(415) 555-2671`, are listed separately and never charged. Spaces, dashes, brackets and a leading `00` are all fine, and one number written two ways is checked once. Always include + and the country code: a number written without it may be read as another country's number.
2. **Checks WhatsApp registration** — confirms whether the number is a real, registered WhatsApp account.
3. **Scrapes the public WhatsApp profile** — for a registered number it pulls the public **profile picture URL**, the **about/status** text, the **country**, how many **devices** are linked, and whether the account is **personal** or a **WhatsApp Business** account.
4. **Extracts the full WhatsApp Business profile** — for business accounts it also captures the **business name, description, every category, contact email, website(s), address, GPS coordinates, time zone, opening hours, how long they've been on WhatsApp, and any linked Facebook page** in a structured `business` object.

### What data does it scrape?

Each result is **one row per phone number**:

| Data | Details |
|---|---|
| 📱 **Number** | the phone number that was checked, in E.164 digits |
| ✅ **Has WhatsApp** | whether the number is a registered WhatsApp account (`true` / `false`) |
| 🏷️ **Account type** | `personal` or `business` (WhatsApp Business) |
| 🌍 **Identity** | `identity` object: the **country** WhatsApp reports for the account |
| 💬 **About / status** | `profile.about` — the public about text, with `profile.aboutRestricted` telling you when privacy settings hid it |
| 🖼️ **Profile picture** *(add-on)* | `profile.picUrl` — a permanent link that never expires, plus `profile.picHash` so you can tell whether the picture changed since your last run. Off by default; turn the add-on on to include it |
| 📱 **Devices** | `devices` object: how many devices are linked, and whether the number runs on the **WhatsApp Business Platform** (Cloud API) |
| 🏢 **Business profile** | nested `business` object for Business accounts: **name**, description, **all categories**, email, websites, address, **latitude/longitude**, time zone, opening hours, **how long they've been on WhatsApp**, cart/commerce flags, and a **linked Facebook page with follower count** |
| 📡 **Carrier info** *(add-on)* | `carrierInfo` object: the **mobile carrier**, **line type** (mobile, fixed line, voip…), `isMobile`, country, region and time zone of the number itself — returned whether or not the number is on WhatsApp. Off by default; turn the add-on on to include it |

> Fields hidden by a user's WhatsApp privacy settings come back as `null`. An `about`
> that exists but is private comes back empty with `profile.aboutRestricted: true`,
> so you can tell "no status" apart from "status hidden".

### Features

#### 🖼️ Public WhatsApp profile scraping

Beyond a simple yes/no, the Actor scrapes the public **profile picture URL**, the **about/status text**, and the **account type** (personal or WhatsApp Business) for every registered number — the data you'd otherwise have to open each chat to see.

#### 🏢 WhatsApp Business profile data extraction

Flags whether each number is a **WhatsApp Business** account or personal, and for business accounts scrapes the full public profile — **business name, description, every category, email, websites, address, GPS coordinates, time zone, opening hours, account age, cart/commerce flags, and a linked Facebook page with its follower count** — straight into a nested `business` object. It's a ready-made **WhatsApp Business lead scraper**.

#### 🛰️ Business-API detection

`devices.hosted` tells you when a number is served by the **WhatsApp Business Platform (Cloud API)** rather than someone's phone — a strong signal that you're looking at a real business integration rather than a personal account with a business label.

#### ✅ Bulk WhatsApp number validation

Pass up to **1,000 phone numbers per run** and the Actor checks each one independently. **Invalid numbers are listed separately and never charged**, and the rest of the list carries on, so a single bad number never kills the whole job — ideal for validating and enriching large phone lists in one go.

#### 🧹 Phone-list cleaning & lead verification

Filter a raw phone list down to numbers that **actually have WhatsApp**, so your outreach reaches real, reachable people and your message credits aren't wasted on bounces.

#### 📤 Clean, structured output

Results are written to a dataset you can export as **JSON, CSV, Excel, HTML, or XML** — one row per phone number, ready to drop into a spreadsheet, CRM, or automation.

### How to scrape WhatsApp profile data

1. Add the phone numbers you want to look up, each with **+ and the country code** (e.g. `+14155552671`). Up to 1,000 numbers per run.
2. Click **Start** and let the Actor run.
3. Download the scraped results from the **Storage** tab as JSON, CSV, or Excel — or push them anywhere with Apify integrations.

### Input example

```json
{
    "numbers": ["+14155552671", "+14155552672"]
}
```

Provide numbers in international **E.164 format** (e.g. `+14155552671`), up to 1,000 per run. See the **Input** tab for the full schema.

### Output example

The Actor returns **one record per number**:

```json
{
    "number": "14155552671",
    "hasWhatsApp": true,
    "accountType": "business",
    "identity": {
        "countryCode": "US",
        "lid": "123456789012345@lid"
    },
    "profile": {
        "about": "Hey there! I am using WhatsApp.",
        "aboutRestricted": false,
        "picUrl": "https://cdn.wtsp.dev/profile-pictures/3f9c2a7e-5b1d-4e8a-9c6f-0d2b4a8e1f73.jpg?v=tkphavoFaBWOiNjZo_B5L-uG8Bw4BjhRDNPgDs-1vp4",
        "picHash": "tkphavoFaBWOiNjZo/B5L+uG8Bw4BjhRDNPgDs+1vp4="
    },
    "devices": {
        "count": 2,
        "hosted": true
    },
    "business": {
        "displayName": "Fresh Market",
        "description": "Fresh groceries delivered daily.",
        "categories": ["Grocery Store", "Food delivery"],
        "email": "hello@example.com",
        "websites": ["https://example.com"],
        "address": "123 Market St, San Francisco, CA",
        "latitude": 37.7749,
        "longitude": -122.4194,
        "timezone": "America/Los_Angeles",
        "hours": [
            { "day": "mon", "mode": "specific_hours", "openTime": "09:00", "closeTime": "17:00" }
        ],
        "memberSince": "Joined in January, 2026",
        "memberSinceTs": 1768942697,
        "businessType": "smb",
        "verifiedLevel": "unknown",
        "cartEnabled": true,
        "commerceExperience": "none",
        "automatedType": "unknown",
        "linkedFacebookPage": { "id": "100000000000000", "name": "Fresh Market", "likes": 44 }
    }
}
```

Fields are grouped so you only get what applies. `identity`, `profile` and `devices`
are returned for every registered number; `business` is `null` for **personal**
accounts, since WhatsApp publishes none of it for them — including the name.

Numbers **not** on WhatsApp return `{ "hasWhatsApp": false }` and none of the four objects — only `carrierInfo`, when that add-on is on, since it describes the number rather than the account.

Numbers we **couldn't check** — bad format, or WhatsApp wouldn't answer — get **no row at all**, so a blank result can never be mistaken for a genuine `false`. They're listed in the **`FAILED_NUMBERS`** record in the run's key-value store (**Numbers we couldn't check** on the Output tab) with the reason for each, and **they are not charged**. The record is there on every run — an empty list when every number was checked. If none of the numbers is a valid phone number, the run finishes with a message that says how to write them. If WhatsApp didn't answer for any number, the run fails instead of finishing with an empty dataset. A spending limit too low for even one number ends the run with an empty dataset and a status message that says so.

```json
[
    {
        "number": "12345",
        "reason": "Not a valid phone number. Start it with + and the country code, e.g. +14155552671."
    }
]
```

When the run finishes, its status message sums it up, for example: *Checked 48 numbers: 31 on WhatsApp. 2 could not be checked and were not charged (listed in FAILED_NUMBERS).*

### Input reference

| Field | Type | Description |
|---|---|---|
| `numbers` | array | Phone numbers to scrape, each with + and the country code (e.g. `+14155552671`). Up to 1,000 per run. Invalid entries are listed in `FAILED_NUMBERS` and never charged. |
| `pictures` | boolean | Add each account's profile picture (`picUrl`/`picHash`) as an add-on. Default **off**; priced per checked number while on, whether or not the account has a picture. |
| `carrierInfo` | boolean | Add each number's carrier, line type, country, region and time zone in a `carrierInfo` object as an add-on. Default **off**. Returned for every checked number, on WhatsApp or not; priced only for numbers with a carrier record. |

#### Profile pictures

**Profile pictures are an add-on**, off by default. Turn them on when you need the image; leave them off for faster, lower-cost runs when you just need names, categories, and contact details.

```json
{
    "numbers": ["+14155552671", "+14155552672"],
    "pictures": true
}
```

#### Carrier info

**Carrier info is an add-on**, off by default and priced only for numbers with a carrier record. Turn it on to learn which **mobile network** each number belongs to, whether it is a **mobile, landline or VoIP** line, and the number's country, region and time zone. It comes from the number itself, not from WhatsApp, so it is returned for every checked number — including those that are **not** on WhatsApp — which makes it a quick way to tell a mobile that simply hasn't installed WhatsApp from a landline that never could.

```json
{
    "numbers": ["+14155552671", "+61491570156"],
    "carrierInfo": true
}
```

Each row then carries a `carrierInfo` object:

```json
{
    "number": "61491570156",
    "hasWhatsApp": false,
    "carrierInfo": {
        "carrier": "Telstra",
        "country": "AU",
        "countryName": "Australia",
        "flag": "🇦🇺",
        "countryCallingCode": "+61",
        "lineType": "mobile",
        "isMobile": true,
        "location": null,
        "timezones": ["Australia/Sydney"]
    }
}
```

`carrier` is `null` where the country publishes no carrier data, and `isMobile` is `null` where a country hands mobiles and landlines out of one shared range (the US, for example), so "unknown" is never reported as "no".

### Run it from your code

Start a run and get the rows back in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/vero-api~whatsapp-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"numbers": ["+14155552671", "+353830648858"], "pictures": true}'
```

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('vero-api/whatsapp-scraper').call({
    numbers: ['+14155552671', '+353830648858'],
    carrierInfo: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((row) => row.business).map((row) => `${row.number}: ${row.business.displayName}`));
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("vero-api/whatsapp-scraper").call(run_input={
    "numbers": ["+14155552671", "+353830648858"],
})
for row in client.dataset(run.default_dataset_id).iterate_items():
    print(row["number"], row["hasWhatsApp"])
```

### Use it from your AI assistant (MCP)

Ask Claude, ChatGPT, Cursor, VS Code, or any assistant that supports MCP to look up numbers in plain words. Add this server to its MCP settings:

```json
{
    "mcpServers": {
        "whatsapp-scraper": {
            "url": "https://mcp.apify.com?tools=vero-api/whatsapp-scraper"
        }
    }
}
```

Then just ask, for example:

- *"Which of these 20 numbers are on WhatsApp, and which ones are businesses?"*
- *"Get the business name, email and website for +14155552671."*

### Pricing

**Pay only for the results you get** — one flat price per number scraped. **Profile pictures and carrier info are add-ons**, off by default — switch them on only when you need them. Profile pictures are priced per checked number while the add-on is on; carrier info only for numbers with a carrier record. Numbers that couldn't be checked are free.

| What you get | Price |
|---|---|
| Checked number (on WhatsApp or not, with the full profile) | $6 per 1,000 numbers ($5 on Silver plans and up) |
| Profile picture add-on | +$10 per 1,000 checked numbers |
| Carrier info add-on | +$4 per 1,000 numbers with a carrier record |
| Numbers we couldn't check | Free |

Set a spending limit on the run and the Actor checks only as many numbers as that limit covers, add-ons included, so you always know your maximum cost.

### Why use a WhatsApp scraper?

Scraping WhatsApp profile data and validating numbers before you contact them saves time, money, and your sender reputation. Common use cases for this WhatsApp scraper include:

- **B2B lead generation from WhatsApp Business** — turn a phone number into a company's public **email, website, address, and opening hours**, ready for sales outreach or a directory.
- **Lead enrichment & data cleaning** — attach WhatsApp status, account type, and profile data to raw phone lists, then filter down to numbers that actually have WhatsApp.
- **Marketing & WhatsApp campaigns** — validate audiences before a broadcast to cut bounce rates and avoid wasted message credits.
- **CRM hygiene & enrichment** — de-duplicate and enrich contact records with verified WhatsApp status, account type, and public profile fields.
- **Research & verification** — confirm whether a number is on WhatsApp and capture the public profile details a user has chosen to share.

### Frequently asked questions

#### What can the WhatsApp Scraper extract?

For each number it scrapes the public **profile picture URL**, the **about/status text**, the **country**, the **linked-device count**, the **account type** (personal or WhatsApp Business), and the full **business profile** for Business accounts (name, description, categories, email, websites, address, GPS coordinates, opening hours, account age, linked Facebook page) — plus a `hasWhatsApp` flag for validation.

#### How do I check if a phone number is on WhatsApp?

Add the number in E.164 format to the input and run the Actor. It returns `hasWhatsApp: true` or `false`, plus the scraped public profile data for registered numbers.

#### Can I scrape and validate WhatsApp numbers in bulk?

Yes. Pass up to **1,000 numbers per run** and the Actor processes each one, returning a separate result row per number it could check. Invalid numbers are skipped individually — reported in `FAILED_NUMBERS` and not charged — while the rest of the list carries on. For bigger lists, split them across several runs, or loop over them from your code with the API example above.

#### Do I need to include the country code?

Yes. Write every number with + and its country code, e.g. `+14155552671` rather than `(415) 555-2671`. Spaces, dashes, brackets and a leading `00` are fine. Entries that can't be read as an international number, like `12345` or `(415) 555-2671`, are listed in `FAILED_NUMBERS` and never charged. A number written without its country code may still be read as another country's number, so always start it with + and the country code. If you call the Actor from code, send each number as a text string with its leading `+`, not as a bare number.

#### How long does a run take?

Most runs finish in seconds. A full list of 1,000 numbers takes a few minutes, and the status message shows how many are done while it runs.

#### Can it tell a WhatsApp Business account from a personal one?

Yes. Every registered number is tagged with an `accountType` of `business` or `personal`, and Business accounts also return a nested `business` object with the company's public details.

#### What WhatsApp Business details can it scrape?

For a WhatsApp Business account, the `business` object includes the **business name, description, every category/vertical, contact email, website URLs, physical address, latitude and longitude, time zone, opening hours, when they joined WhatsApp, cart and commerce flags, and any linked Facebook page with its follower count** — everything the business has chosen to publish on its WhatsApp profile. Personal accounts return `business: null`.

#### Can it scrape a person's name?

Only for **business** accounts, as `business.displayName`. Personal accounts do not publish a display name.

#### Can it tell me the carrier or line type of a number?

Yes, with the **carrier info add-on**. Turn on `carrierInfo` and every checked number gets a `carrierInfo` object with its mobile network, line type (mobile, fixed line, voip…), an `isMobile` flag, country, region and time zone. It is derived from the number itself, so you get it for numbers that are not on WhatsApp too.

#### Can it scrape WhatsApp usernames (@handles)?

Not currently. Usernames are not part of the output.

#### In what formats can I export the scraped data?

You can download the dataset as **JSON, CSV, Excel, HTML, or XML** from the Storage tab, or send it to Google Sheets, a database, or any service through Apify integrations.

#### What happens if I set a spending limit?

The Actor checks only as many numbers as your limit covers, counting the add-ons you switched on, then finishes cleanly. The run's status message tells you how many numbers were left unchecked.

#### Is this Actor affiliated with WhatsApp or Meta?

No. It is an independent tool and is not affiliated with, endorsed by, or sponsored by WhatsApp or Meta. It only returns information that account owners have chosen to make public on their profiles.

### More tools from us

- [wtsp.dev](https://wtsp.dev) — the same WhatsApp profile lookups as a plain HTTP API, one number per request, paid per call in USDC or with a prepaid key. Ready for AI agents.
- [WhatsApp Number Checker](https://apify.com/vero-api/whatsapp-number-checker) — just the yes/no: which numbers have WhatsApp.
- [Phone Number Carrier Lookup](https://apify.com/vero-api/phone-carrier-lookup) — carrier, line type, country and time zone for any phone number.
- [Telegram Username Checker](https://apify.com/vero-api/telegram-username-checker) — profiles, channels, groups and bots by username.
- [Email Validator](https://apify.com/vero-api/email-validator) — clean the business emails you collect: typos, dead domains and throwaway addresses.
- [Google Maps Reviews Scraper](https://apify.com/vero-api/google-maps-reviews-scraper) — reviews and ratings for the businesses on your lead list.

# Actor input Schema

## `numbers` (type: `array`):

Phone numbers to look up, each with + and the country code, e.g. +14155552671. Spaces, dashes and brackets are fine, and 00 works in place of +. Entries that can't be read as an international number, such as 12345 or (415) 555-2671, are listed separately and never charged. A number without its country code may be read as another country's number. Up to 1,000 numbers per run.

## `pictures` (type: `boolean`):

Turn on to add each account's profile picture link, plus a hash that tells you when it changed since your last run. An add-on, priced per checked number while on, whether or not the account has a picture. Off by default.

## `carrierInfo` (type: `boolean`):

Turn on to add each number's mobile carrier, line type (mobile, fixed line, voip…), country, region and time zone in a carrierInfo object. Returned for every checked number, on WhatsApp or not. An add-on, priced only for numbers with a carrier record. Off by default.

## Actor input object example

```json
{
  "numbers": [
    "+15558681494",
    "+353830648858"
  ],
  "pictures": false,
  "carrierInfo": false
}
```

# Actor output Schema

## `results` (type: `string`):

One row per checked phone number with every field: number, hasWhatsApp, accountType, identity (country), profile (about, picture), devices, business (name, categories, email, websites, address, hours) and carrierInfo when that add-on is on.

## `failed` (type: `string`):

Numbers that couldn't be checked, each with its number and a reason. Never charged. An empty list when every number was checked.

# 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 = {
    "numbers": [
        "+15558681494",
        "+353830648858"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vero-api/whatsapp-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 = { "numbers": [
        "+15558681494",
        "+353830648858",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("vero-api/whatsapp-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 '{
  "numbers": [
    "+15558681494",
    "+353830648858"
  ]
}' |
apify call vero-api/whatsapp-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vero-api/whatsapp-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/GrHirQh6poUynVShs/builds/9AxpaIoVDD1fVk2ZT/openapi.json
