Lead verification checks the contact details on a form before your team spends time on them. One request checks the phone number (line type, messaging-app presence, optionally live status) and the e-mail address (whether the mailbox exists). You can score leads, route doubtful ones to review and ask for corrections while the person is still on the page.
Why verify leads at the form?
Bad leads cost sales time, dialler minutes and ad budget, and they distort your conversion data. Common problems:
- Typos. A digit missing from the phone number or a misspelled e-mail domain. Catching these on the form lets the person fix them straight away.
- Made-up details. Numbers such as
555 0100-style fillers, or e-mail addresses at real providers that don't exist. - Numbers that can't be reached the way you plan. A fixed line when your process relies on SMS, or a VoIP number where you expect a mobile.
- Numbers that aren't in service. A number can pass a format check and still not be assigned. The HLR lookup asks the home network.
- Bots and paid form fillers. They often reuse patterns. A burst of generated numbers or addresses is exactly what our anti-enumeration rules reject.
A useful combination for most forms is the carrier lookup plus WhatsApp for the number, and the e-mail mailbox check for the address. For US and Canadian leads handled in batches, the US/CA carrier lookup adds current-carrier data. For property and insurance forms specifically, see lead quality for real estate and insurance.
How does the workflow look?
- When the form is submitted, call
POST /v1/lookupwithnumbers,emailsandchecks: ["carrier", "whatsapp", "email"]. - Result rows come numbers first, then e-mails. Each row has a
kind(phoneoremail) and achecksmap. - Score the lead with the table below. Store the score and the
checked_attime in your CRM, not the whole response. - If the score is low, ask for a correction on the form ("please check your number") or send the lead to manual review.
- For leads that arrive in batches, for example from events or partners you have a lawful basis for, use a bulk job with the same checks. Download the CSV and import it back into your CRM.
Which checks answer which question?
| Check | What it answers | When to run it | Link |
|---|---|---|---|
carrier | Mobile, fixed line or VoIP? Which carrier? | Every lead | Carrier lookup |
whatsapp | Is the number registered on WhatsApp? | Every lead, as a weak "in use" signal | WhatsApp number check |
email | Does the mailbox exist at a major webmail provider? | Every lead with an e-mail | E-mail mailbox check |
hlr | Is the mobile number assigned and reachable now? | High-value leads | HLR lookup |
network.carrier_us (bulk) | Current carrier of US/CA numbers | US/CA batches | US/CA carrier lookup |
What should you do with each result?
| Signals | Suggested action |
|---|---|
| Mobile line and a mailbox that exists | Accept the lead |
Mobile line, messenger account, mailbox check unknown (e.g. a company domain) | Accept. Unknown is not negative |
number_status: invalid_number or email_status: invalid_email | Ask for a correction on the form |
Mailbox registered: false | Ask for a correction or review. The address will probably bounce |
HLR status: invalid | Ask for a correction; the number isn't assigned |
line_type is voip, no messenger account, mailbox missing | Review before sales spends time on it |
Many signals unknown | Accept with normal handling. Missing data is not evidence of fraud |
How do you turn results into a lead score?
Keep the score simple and explainable, so sales trusts it and you can tune it. One approach is to start every lead at a neutral value and adjust for conclusive answers only:
- add points for a mobile line, an existing mailbox and, where you use it, a reachable HLR status;
- subtract points for a mailbox that doesn't exist, an unassigned number or a line type your process can't use;
- leave the score unchanged for every
unknown, so missing data never pushes a lead down.
Combine the result with what you already know, such as the campaign, the page and the time taken to fill the form, and review the thresholds every few weeks against leads that actually converted.
How much does it cost?
Each check on each identifier is billed separately, and only when the answer is conclusive. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). The request below runs three checks and can bill up to three. Company e-mail domains that the mailbox check doesn't cover return unknown and cost nothing. See pricing for current rates, including the HLR lookup.
Real-time checks at the form cost more per check than bulk jobs, but they let the person fix a mistake straight away. For batches, the bulk price applies. If the same lead submits twice inside the freshness window, the second check comes free from your account's cache.
What does a request look like?
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"emails":["not-registered@test.mobilevalidate.com"],"checks":["carrier","whatsapp","email"]}'// npm install mobilevalidate · Node.js 20+ · save as check.mjs and run: node check.mjs
import { MobileValidate } from "mobilevalidate";
// Omit apiKey to read MOBILEVALIDATE_API_KEY from the environment.
const mv = new MobileValidate({ apiKey: "mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" });
const { data, error } = await mv.lookup({
numbers: ["+447700900001"],
emails: ["not-registered@test.mobilevalidate.com"],
checks: ["carrier", "whatsapp", "email"],
});
if (error) console.error(error.code, error.message);
else console.dir(data.results, { depth: null });// Node.js 18+, Deno, Bun or the browser console (ES module: save as .mjs or use "type": "module").
const res = await fetch("https://api.mobilevalidate.com/v1/lookup", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({
numbers: ["+447700900001"],
emails: ["not-registered@test.mobilevalidate.com"],
checks: ["carrier", "whatsapp", "email"],
}),
});
console.log(res.status, res.headers.get("x-request-id"));
console.dir(await res.json(), { depth: null });# pip install mobilevalidate-sdk
from mobilevalidate import MobileValidate
# Omit api_key to read MOBILEVALIDATE_API_KEY from the environment.
mv = MobileValidate(api_key="mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
result = mv.lookup(
numbers=["+447700900001"],
emails=["not-registered@test.mobilevalidate.com"],
checks=["carrier", "whatsapp", "email"],
)
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/lookup');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'numbers' => ['+447700900001'],
'emails' => ['not-registered@test.mobilevalidate.com'],
'checks' => ['carrier', 'whatsapp', 'email'],
]),
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), PHP_EOL, $response, PHP_EOL;package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"numbers":["+447700900001"],"emails":["not-registered@test.mobilevalidate.com"],"checks":["carrier","whatsapp","email"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/lookup", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}require "net/http"
require "json"
uri = URI("https://api.mobilevalidate.com/v1/lookup")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"numbers" => ["+447700900001"],
"emails" => ["not-registered@test.mobilevalidate.com"],
"checks" => ["carrier", "whatsapp", "email"]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code, res.bodyRuns as pasted with the public sandbox key, which answers the test values only. In the SDKs, omit the key to use MOBILEVALIDATE_API_KEY.
Response (excerpt, test mode: the checks of the phone row, then of the e-mail row):
[
{
"network.carrier": {
"service": "network.carrier", "status": "completed", "registered": true,
"attributes": {"line_type": "mobile", "carrier": "Test Carrier", "country": "GB"},
"confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:23.538Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
},
"whatsapp.registered": {
"service": "whatsapp.registered", "status": "completed", "registered": true, "attributes": null,
"confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:23.538Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
}
},
{
"email.valid": {
"service": "email.valid", "status": "completed", "registered": false, "attributes": null,
"confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-25T14:28:23.538Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
}
}
]The phone looks fine and the mailbox doesn't exist, so the form should ask the person to check their e-mail address.
What limits and rules apply?
A lookup takes up to 100 numbers and e-mails in total and up to 20 checks. You need at least one check for each kind of identifier you send. Addresses are normalized by trimming spaces and lowercasing, and nothing else is changed. Requests that look like sequential number ranges or generated e-mail lists are rejected. That includes 20 or more addresses on one domain that differ only by digits. See e-mail checks.
What about consent and compliance?
Say in your privacy notice that you verify contact details to prevent fraud and keep your records accurate. Verify only leads you have a lawful basis to process. Don't use the checks to enrich people you have no relationship with, and remember that a passed check is not consent to marketing. In the US, calls and texts to mobile numbers fall under the TCPA (47 CFR 64.1200); in the EU and UK, contact data is personal data under the GDPR (see is a phone number personal data?). This is not legal advice. People can object through the opt-out form.
What are the common mistakes?
- Rejecting leads on
unknown. Company domains and slow networks return unknown. Accept and handle normally. - Blocking every VoIP number. It's one weak signal, not a verdict.
- Checking only in batch. The person can't fix a typo after they've left the page.
- Using the checks to enrich. We return no names or profiles, and checking people you have no relationship with breaks our acceptable use policy.
- Storing full responses. Keep the score and
checked_at.
Frequently asked questions
Can I check a phone number and an e-mail address in the same request?
Yes. Send numbers and emails together (up to 100 in total) with at least one phone check and one e-mail check. Phone checks run on the numbers and e-mail checks run on the addresses.
Does a missing messenger account mean the lead is fake?
No. Many real people don't use a given messenger. A missing account is one weak signal. It carries more weight when combined with other checks, such as a non-mobile line type or a mailbox that doesn't exist.
Which e-mail addresses can the mailbox check answer for?
The e-mail mailbox check covers major webmail providers. For other domains it returns unknown with the reason UNSUPPORTED_PROVIDER, and that result is not charged.
Do you return the lead's name or social profile?
No. Checks answer only yes, no or unknown, plus data such as line type or carrier. Names, photos and profiles are never returned.
Can I verify leads I bought from a third party?
Only if you have a lawful basis to process them and the people agreed to be contacted by you. The checks can't make a non-consented list usable for marketing.
Should I check leads at the form or in a nightly batch?
At the form when you can: that is the only moment the person can fix a typo. Use bulk jobs for leads that arrive in batches, such as event lists, where the bulk price applies.
Is the HLR lookup useful for leads?
For high-value leads, yes. It asks the home network whether the mobile number is assigned and reachable, which catches numbers that are valid in format but not in service. It is billed per conclusive answer; the pricing page shows the current price.
Related
- ServiceCheck if an e-mail mailbox exists
- ServicePhone carrier lookup by number: carrier and line type API
- ServiceHLR lookup API: live mobile network status
- ServiceCheck if a phone number is registered on WhatsApp
- ServiceUS and Canada carrier lookup in bulk (ported numbers)
- Use caseLead quality for real estate and insurance
Guides on this topic
All articlesDeliverability
Number recycling and phone-list decay: how often to re-check your list
Disconnected phone numbers are reassigned to new people, so every list decays. How recycling works, what the US rules say, and a re-check cadence recipe.
7 min read
Guides
The TCPA "revoke-all" opt-out rule: what the FCC's 2026 order changes
What the TCPA revoke-all opt-out rule required, why it was delayed to 31 January 2027, how the FCC's September 2026 order (FCC 26-67) replaces it, and an opt-out data model. Not legal advice.
11 min read
Guides
Yahoo email checker: how to check if a Yahoo Mail address exists, one at a time or in bulk
To check if a Yahoo email exists, send a test message and read the bounce, or run an account check in bulk. SMTP probes fail at Yahoo; here is why.
15 min read


