In fintech and lending onboarding, phone and e-mail checks confirm contactability: that the applicant's mobile is assigned and reachable and the mailbox exists. They also add risk signals such as line type, porting and, where enabled, spam reports. This is not identity verification and not KYC. It sits beside your regulated checks and helps stop fake applications early.
What is contactability, and why does it matter?
A financial account depends on reaching the customer. You send one-time passcodes to their phone, statements and notices to their e-mail, and some messages are legally required. If the contact details given at onboarding don't work, problems appear later, when they're harder to fix:
- Passcodes that never arrive. An applicant on an unreachable or unassigned number can't finish sign-in or confirm a payment, and your support team takes the call.
- Notices that bounce. A mailbox that doesn't exist means account notices and statements go nowhere. You then have to find another way to meet your communication duties.
- Fake and synthetic applications. Applications built on made-up details often use numbers and addresses that don't hold up: an unassigned number, a mailbox that doesn't exist, a line type that doesn't fit the rest of the application.
- Account takeover later on. A number that was ported to another network since onboarding, when your records don't expect it, is a reason for an extra step before a sensitive change.
Checking contact details at onboarding gives you a clean starting record and early warning signals, without asking the applicant for anything more.
What this is not: identity verification or KYC
These checks are not identity verification and are not KYC. They describe a phone number or an e-mail address. They don't tell you who holds the number, and they never return names, photos, addresses or profiles. A reachable number with a live mailbox can still belong to a fraudster, and a genuine applicant can have an unreachable phone on the day they apply.
Run your regulated customer due diligence (identity documents, sanctions and PEP screening, AML monitoring) with the providers and processes your regulator expects. Use contactability checks alongside them, not instead of them. Our acceptable use policy says the same: results are signals at a point in time, not proof of identity or ownership, and they can't replace KYC or AML checks.
How does it work?
- Normalize the number. Parse it to E.164 using the applicant's country. Unparseable input comes back as
number_status: invalid_number, free, so the form can ask for a correction. - Check at submission. Call
POST /v1/lookupwith the number and e-mail andchecks: ["carrier", "hlr", "email"]. Addspamif it's enabled for your account. - Score contactability. Is the mobile reachable, and does the mailbox exist? If not, ask the applicant to correct or confirm the detail before you continue.
- Weigh risk signals. Combine line type, porting and spam reports with your own device, session and application data.
- Step up, don't auto-decline. Doubtful combinations go to extra verification (confirm the e-mail, a call-back) or to manual review.
- Store the baseline. Keep
mcc_mnc,line_typeandchecked_atwith the account. Later, compare a fresh MNP lookup with this baseline before a password reset, a new payee or a payout. - Keep results out of credit decisions. Don't feed them into creditworthiness, pricing or limit decisions.
Which checks should you use?
| Check | What it answers | When to use it | Service |
|---|---|---|---|
hlr | Is the mobile assigned and reachable now? Ported? Current network? | At onboarding, so passcodes and notices can arrive | HLR lookup |
carrier | Mobile, fixed line or VoIP? Current carrier? | To spot numbers that can't take SMS, and VoIP lines that deserve a step-up | Carrier lookup |
email | Does the mailbox exist at a major webmail provider? | Before sending the account confirmation and notices | E-mail mailbox check |
mnp | Was the number ported, and to which network? | Baseline at onboarding; re-check before sensitive changes | MNP lookup |
spam | Does the number appear in spam and nuisance-call reports? | As one fraud signal, where enabled (limited access) | Spam reputation |
The HLR lookup already includes the porting flag and current network, so you don't need mnp at onboarding if you run hlr. Use mnp for the cheaper re-checks later.
What happens after onboarding?
Contact details don't stay right forever. Customers change numbers, and attackers who take over an account try to change where passcodes and notices go. Three moments deserve a fresh check:
- A new phone number or e-mail on the account. Check the new details the same way as at onboarding, and confirm the change through the old ones. See account security.
- Sensitive actions: a password reset to a new destination, a new payee, a payout to a new account. A cheap MNP lookup compared with the stored baseline shows whether the number moved to another network in the meantime.
- Failed contact. When passcodes stop arriving or notices bounce, re-check before assuming the customer is at fault.
Use max_age: 0 for these checks, so the answer isn't served from an older cache entry.
Example request
With a test key, +447700900006 is a reachable mobile that was ported, and [email protected] has a mailbox. See test values.
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900006"],"emails":["[email protected]"],"checks":["carrier","hlr","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: ["+447700900006"],
emails: ["[email protected]"],
checks: ["carrier", "hlr", "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: ["+447700900006"],
emails: ["[email protected]"],
checks: ["carrier", "hlr", "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=["+447700900006"],
emails=["[email protected]"],
checks=["carrier", "hlr", "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' => ['+447700900006'],
'emails' => ['[email protected]'],
'checks' => ['carrier', 'hlr', '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":["+447700900006"],"emails":["[email protected]"],"checks":["carrier","hlr","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" => ["+447700900006"],
"emails" => ["[email protected]"],
"checks" => ["carrier", "hlr", "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-28T22:40:26.967Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
},
"number.hlr": {
"service": "number.hlr", "status": "completed", "registered": true,
"attributes": {"status": "reachable", "ported": true, "roaming": false, "network": "Test Mobile Two", "mcc_mnc": "23410", "country": "GB"},
"confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-28T22:40:26.967Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
}
},
{
"email.valid": {
"service": "email.valid", "status": "completed", "registered": true, "attributes": null,
"confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-28T22:40:26.967Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
}
}
]A reachable mobile and a live mailbox: the applicant is contactable. The number was ported at some point, which on its own is normal, since people switch operators all the time. Store mcc_mnc: 23410 as the baseline, so a later change stands out.
What should you do with each result?
| Signals | Suggested action |
|---|---|
Mobile, HLR reachable, mailbox exists | Contactable. Continue onboarding |
HLR unreachable | Ask the applicant to confirm the number; offer e-mail confirmation |
HLR invalid or number_status: invalid_number | Ask for a correction before continuing |
Mailbox registered: false | Ask for a working address before sending notices |
line_type: voip with otherwise normal signals | Continue, with an extra verification step |
voip, mailbox missing and a new device | Manual review |
Spam risk_level: high (where enabled) | Manual review. Never an automatic decline on this alone |
Current mcc_mnc differs from the stored baseline | Step up before a reset, new payee or payout |
Any check unknown | Decide on your other data. Unknown is not negative and is free |
How much does it cost?
The HLR lookup is $0.005 per number and the MNP lookup $0.001 per number. The carrier lookup, the mailbox check and spam reputation are priced per check; see pricing. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Onboarding runs a few checks per applicant, and repeat checks of the same number inside the freshness window come from your account's cache for free.
What about consent and compliance?
Tell applicants in your privacy notice that you verify contact details to prevent fraud and to be able to reach them. Check only details the applicant gave you. Results are signals at a point in time, so store checked_at with every decision.
Keep three boundaries. First, this is not KYC; run your regulated checks separately. Second, don't use results for eligibility decisions about credit or insurance, or as a consumer report; the acceptable use policy forbids it. Third, there are no reverse lookups: checks never tell you who holds a number. Numbers from sanctioned countries and regions are never checked. People can object through the opt-out form.
What are common mistakes?
- Calling it identity verification. A reachable number is not a verified person. Keep your KYC separate.
- Auto-declining on one signal. VoIP numbers, ported numbers and unknowns are common among genuine applicants. Step up instead.
- Letting results leak into credit models. Keep them in fraud and contactability logic only.
- Checking once and never again. Store a baseline and re-check before sensitive account changes.
- Treating
no_reportsas safe. It means no reports are held, nothing more. - Storing full responses. Keep the decision, the baseline fields and
checked_at.
Frequently asked questions
Is this identity verification or KYC?
No. The checks describe a phone number or e-mail address: whether it is reachable, what kind of line it is, whether the mailbox exists and, where enabled, whether the number has spam reports. They don't say who holds the number and can't replace KYC or AML checks. Run your regulated identity checks separately.
What does contactability mean in onboarding?
Whether you can actually reach the applicant on the details they gave: a mobile number that is assigned and reachable, and a mailbox that exists. You need that for one-time passcodes, account notices, servicing and any communication the law requires you to send.
Can I use the results in credit decisions?
No. The acceptable use policy forbids using results to make eligibility decisions about credit, employment, housing or insurance, or as a consumer report. Use them for fraud prevention and to make sure you can reach the applicant, and route doubtful cases to extra verification or manual review.
Should I reject applicants with VoIP numbers?
Not automatically. Many genuine people use VoIP numbers. A VoIP line is a reason for an extra verification step, such as confirming the e-mail or completing another check, not a rejection on its own.
Does the service detect SIM swaps?
No. The MNP and HLR lookups show whether a number was ported and which network serves it now, which helps you notice a change against what you stored earlier. They don't report SIM changes.
Is spam reputation available?
It is in limited access, for internal customers only for now. The other checks on this page are available to every customer with API access.
What happens when a check can't answer?
The result is unknown and free. Treat unknown as missing information, never as a negative signal, and decide on your other onboarding data.


