Courier onboarding checks confirm that a new driver or gig worker gave a reachable mobile number and a working e-mail before their first shift. The same checks help delivery notifications: the line type, live reachability and messaging-app registration tell you which consented channel will reach the recipient. They describe contact details only, never identity.
Why does contactability matter in delivery?
Last-mile delivery runs on short messages sent at the right moment: shift offers, route changes, pickup codes, "your parcel is out for delivery", "the driver is two stops away", "we missed you". When a message doesn't arrive, the result is a failed shift, a missed delivery or a second attempt, and each of those costs far more than the message.
There are two groups of contacts to get right:
- Couriers and gig workers. They sign up in an app, often on a phone, and often in a hurry. Mistyped numbers, work numbers that are fixed lines, and e-mail addresses with a typo in the domain are common. Dispatch finds out on the first shift.
- Recipients. They type a number at checkout. It may be a landline, a colleague's desk phone, or a number with a digit missing. Some recipients prefer a messaging app over SMS and said so.
The carrier lookup tells you whether a number is mobile. The HLR lookup tells you whether a mobile number is assigned and reachable right now. Messaging-app checks such as WhatsApp tell you whether the number is registered there. The e-mail mailbox check says whether the mailbox exists at major webmail providers.
How does the workflow look?
Courier onboarding:
- When an applicant submits their details, call
POST /v1/lookupwith their number and address andchecks: ["carrier", "hlr", "email"]. - If the number is invalid or not assigned, or the mailbox doesn't exist, ask for a correction in the sign-up flow.
- If the line is fixed, ask for a mobile number that can receive shift offers.
- Store
line_typeandchecked_atwith the courier profile. Re-check when a dispatch message fails.
Delivery notifications:
- At checkout, run a quick
carriercheck on the recipient's number and ask for a correction if it isn't a mobile or is invalid. - If the recipient opted in to app notifications, check the relevant app once and store the result.
- On delivery day, send on the consented channel with
registered: true, and fall back to SMS for a mobile line. - Before a time-critical message, such as "driver arriving", run
hlrif the previous message failed. If the phone isunreachable, use another agreed channel. - Refresh in bulk for repeat customers; iMessage and RCS checks run in bulk jobs only.
Which checks answer which question?
| Check | What it answers | When to run it | Link |
|---|---|---|---|
carrier | Mobile, fixed line or VoIP? | Courier sign-up; checkout | Carrier lookup |
hlr | Is the mobile line assigned and reachable now? | Courier sign-up; before time-critical messages | HLR lookup |
email | Does the mailbox exist at a major webmail provider? | Courier sign-up; account creation | E-mail mailbox check |
whatsapp, viber, telegram | Is the number registered on this app? | When the recipient opted in to app notifications | WhatsApp, Viber |
imessage, rcs (bulk only) | Can the number receive iMessage or RCS? | Periodic bulk refresh | iMessage, RCS |
What should you do with each result?
| Result | Courier onboarding | Delivery notifications |
|---|---|---|
line_type: mobile, HLR reachable | Continue sign-up | Send SMS or the consented app |
line_type: fixed_line | Ask for a mobile number | No SMS; e-mail or a consented app |
HLR status: unreachable | Accept; the phone may simply be off | Try another agreed channel; retry later |
HLR status: invalid | Ask for a correction | Ask the recipient to correct the number |
Mailbox registered: false | Ask for a correct address | Don't rely on e-mail |
App registered: true with opt-in | Optional dispatch channel | Eligible for app notifications |
Any check unknown | Continue; unknown is free and neutral | Send as usual |
What does a request look like?
This test-mode request checks an applicant's number and e-mail. +447700900002 answers unreachable for the HLR lookup, and [email protected] has an existing mailbox.
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900002"],"emails":["[email protected]"],"checks":["hlr","email"],"wait":10}'// 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: ["+447700900002"],
emails: ["[email protected]"],
checks: ["hlr", "email"],
wait: 10,
});
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: ["+447700900002"],
emails: ["[email protected]"],
checks: ["hlr", "email"],
wait: 10,
}),
});
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=["+447700900002"],
emails=["[email protected]"],
checks=["hlr", "email"],
wait=10,
)
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' => ['+447700900002'],
'emails' => ['[email protected]'],
'checks' => ['hlr', 'email'],
'wait' => 10,
]),
]);
$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":["+447700900002"],"emails":["[email protected]"],"checks":["hlr","email"],"wait":10}`)
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" => ["+447700900002"],
"emails" => ["[email protected]"],
"checks" => ["hlr", "email"],
"wait" => 10
})
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):
[
{
"number.hlr": {
"service": "number.hlr", "status": "completed", "registered": true,
"attributes": {"status": "unreachable", "ported": false, "roaming": false, "network": "Test Mobile", "mcc_mnc": "23415", "country": "GB"},
"confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-29T06:58:33.290Z",
"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-29T06:58:33.290Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
}
}
]The number is assigned but the phone is off right now, and the mailbox exists. Sign-up continues; dispatch should confirm the number with a test message before the first shift. Test the pending path with [email protected] or +447700900004, and poll GET /v1/lookups/{id} if the lookup answers pending.
How much does it cost?
You pay per check, and only for conclusive answers. Unknown, unsupported-country, invalid and duplicate results are free. The HLR lookup costs $0.005 per number; reachable, unreachable and invalid are conclusive and billed. See pricing for the carrier, e-mail and messaging-app rates.
Onboarding checks run once per applicant, so the cost is small next to recruiting and a failed first shift. For notifications, store stable answers such as line type and app registration, and save real-time HLR checks for moments where timing matters. Repeat checks inside the freshness window come from your cache for free, bulk jobs cost less per check for most services, and max_cost caps any single request.
What about consent and compliance?
- Contact data only. The checks don't verify identity, right to work or driving records, and results must not be used for employment decisions. Use them to ask for correct contact details.
- Tell people. Say in your courier and customer privacy notices that you check phone numbers and e-mail addresses so messages reach them.
- Consent per channel. Delivery notifications on a messaging app need the recipient's opt-in and must follow the platform's business rules. Having an account is not consent.
- Service, not marketing. An opt-in to delivery updates doesn't cover promotions.
- No tracking. The HLR lookup returns roaming as yes or no only. Don't use it to monitor couriers; use your own app's consented location features for dispatch.
- Minimize. Store the class and
checked_at, not full responses. People can object through the opt-out form.
What are the common mistakes?
- Rejecting an applicant because of a check. Ask for a correction instead; hiring decisions belong to your own process.
- Treating
unreachableas a wrong number. It means the phone is off or out of coverage right now. The number exists. - Blocking checkout on
unknown. Missing data isn't an error. Let the order through. - Checking every message. Line type and app registration are stable; check once and refresh on failures.
- Switching to an app without opt-in. Recipients who chose SMS should get SMS.
For why SMS sometimes doesn't arrive even to a good number, read why SMS is not delivered.
Frequently asked questions
Why check a courier's phone number at onboarding?
Dispatch depends on reaching the courier within minutes. A check at sign-up catches mistyped numbers, fixed lines that can't receive app codes or texts, unassigned numbers and mailboxes that don't exist, before the first shift is booked.
Is this a background check or identity verification?
No. The checks describe a phone number or e-mail address, not a person. They don't verify identity, right to work, driving history or anything else, and must not be used for employment decisions.
Can I reject an applicant because their number is VoIP?
The results must not be used for employment decisions. Use them to ask for a correction or a different contact number, so dispatch can reach the courier; the hiring decision rests on your own lawful process.
How do contact checks help delivery notifications?
They tell you whether the recipient's number is a mobile line, whether it is reachable now and which messaging apps it is registered on, so the out-for-delivery message goes on a channel the recipient agreed to and can receive.
Should I run an HLR check before every delivery message?
No. Use it where timing matters, such as a missed-delivery or courier-arriving message, or when a previous message failed. Line type and app registration change slowly and can be stored.
What happens when a check returns unknown?
Send as you normally would. Unknown means no conclusive answer, it is never charged, and it says nothing bad about the number.
Do you return where the courier or recipient is?
No. The HLR lookup returns roaming as true or false only. We never return a location, and the service is not a tracking tool.


