E-commerce order verification checks the phone number and e-mail address on an order before you ship. For cash-on-delivery and high-value orders, an HLR lookup shows whether the number is live, and the mailbox check shows whether the address exists. You can fix typos at checkout, hold doubtful orders and send delivery updates on a channel the buyer can receive.
Why check contact details on orders?
The phone number on an order does real work. The courier calls it to arrange delivery. Support uses it when an address is incomplete. For cash on delivery (COD), it's often the only way to confirm the buyer still wants the parcel. The e-mail address carries the order confirmation, the invoice and the tracking link.
When those details are wrong, the costs arrive late and in the wrong place:
- Failed deliveries and returns. A courier who can't reach the buyer may bring the parcel back. For COD you pay shipping both ways and collect nothing.
- Refused COD parcels. Orders placed with made-up or mistyped numbers are harder to confirm before dispatch, so more of them are refused at the door.
- Lost post-purchase communication. A mistyped e-mail means no confirmation, no tracking link and more "where is my order" contacts.
- Wasted notification spend. SMS updates to a fixed line or an unassigned number cost money and reach nobody.
A check before dispatch turns these into cheap, early decisions: ask the customer to correct, confirm by another channel, or ship as normal.
How does it work?
- At checkout, validate format. Parse the phone number to E.164 using the shipping country. Entries that can't be parsed come back as
number_status: invalid_number, free, and you can ask the customer to fix them before they pay. - Decide which orders to check. For example: every COD order, orders above a value threshold, and first orders from new accounts.
- Run the checks. Call
POST /v1/lookupwith the order's number and e-mail andchecks: ["hlr", "email"]. Addwhatsappor another channel check only if the customer opted in to updates there. - Apply your rules. Use the table below: ship, confirm, or hold.
- Confirm doubtful orders. Send a confirmation request by the channel that works: e-mail if the phone failed, SMS if the mailbox doesn't exist. Hold the parcel until the customer answers or a deadline passes.
- Pick the notification channel. Send delivery updates by SMS to reachable mobiles, by e-mail when the mailbox exists, or on a messenger the customer opted into.
- Store the outcome. Keep the decision and
checked_aton the order, not the full response.
For COD orders that wait in a warehouse, you can run the HLR lookup again right before dispatch with max_age: 0 to get a fresh answer.
Which checks should you use?
| Check | What it answers | When to use it | Service |
|---|---|---|---|
hlr | Is the mobile number assigned and reachable right now? | COD and high-value orders, before dispatch | HLR lookup |
carrier | Is it mobile, fixed line or VoIP? | Before sending SMS delivery updates | Carrier lookup |
email | Does the mailbox exist at a major webmail provider? | At checkout, so the confirmation and tracking mail arrive | E-mail mailbox check |
whatsapp | Does the number have a WhatsApp account? | Only for customers who opted in to updates on WhatsApp | WhatsApp check |
viber / rcs | Viber account, or an RCS-capable number? | Choosing a messaging channel where the customer opted in | Viber, RCS |
See channel selection for picking the notification channel in more detail.
Checkout or before dispatch?
Both moments have a job to do, and they answer different questions.
- At checkout, the customer is still on the page. A mailbox that doesn't exist or a number that can't be parsed can be fixed in seconds with an inline message ("please check your e-mail address"). This protects the confirmation e-mail and the tracking link for every order. Keep the checkout call fast: one number, one address, and a short
wait. - Before dispatch, you want to know whether the buyer can be reached today. For COD orders that sat in a queue, an HLR lookup with
max_age: 0gives a fresh answer. If the number is unreachable, send a confirmation by e-mail and hold the parcel for a set time.
If you only do one, do checkout for all orders. Add the dispatch check where a failed delivery costs the most.
Example request
With a test key, +447700900001 is reachable and has a WhatsApp account, and [email protected] has no mailbox. See test values.
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"numbers":["+447700900001"],"emails":["[email protected]"],"checks":["hlr","whatsapp","email"],"metadata":{"order_id":"ORD-1042"}}'// 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: ["[email protected]"],
checks: ["hlr", "whatsapp", "email"],
metadata: { order_id: "ORD-1042" },
});
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: ["[email protected]"],
checks: ["hlr", "whatsapp", "email"],
metadata: { order_id: "ORD-1042" },
}),
});
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=["[email protected]"],
checks=["hlr", "whatsapp", "email"],
metadata={ "order_id": "ORD-1042" },
)
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' => ['[email protected]'],
'checks' => ['hlr', 'whatsapp', 'email'],
'metadata' => [ 'order_id' => 'ORD-1042' ],
]),
]);
$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":["[email protected]"],"checks":["hlr","whatsapp","email"],"metadata":{"order_id":"ORD-1042"}}`)
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" => ["[email protected]"],
"checks" => ["hlr", "whatsapp", "email"],
"metadata" => { "order_id" => "ORD-1042" }
})
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": "reachable", "ported": false, "roaming": false, "network": "Test Mobile", "mcc_mnc": "23415", "country": "GB"},
"confidence": "high", "confidence_score": 0.99, "checked_at": "2026-09-28T22:40:26.882Z",
"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-28T22:40:26.882Z",
"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-28T22:40:26.882Z",
"cached": false, "age_seconds": 0, "billed": false, "reason": null, "poll_after_ms": null
}
}
]The phone is reachable, so the courier can call. The mailbox doesn't exist, so the confirmation e-mail would bounce. Show a "please check your e-mail address" message on the order page, and send tracking by SMS, or by WhatsApp if the customer opted in. metadata is echoed back, so you can match the result to the order.
What should you do with each result?
| Signals | Suggested action |
|---|---|
HLR reachable and mailbox exists | Ship as normal |
HLR reachable, mailbox registered: false | Ship; send tracking by SMS and ask for a corrected e-mail |
HLR unreachable on a COD order | Confirm by e-mail first; re-check before dispatch |
HLR invalid or number_status: invalid_number | Hold. Ask the customer to correct the number |
line_type: fixed_line | Don't send SMS updates; the courier can still call |
Messenger registered: true and the customer opted in | Send delivery updates there if it suits you |
Any check unknown | Ship as normal. Unknown is not negative and is free |
Tune the actions to your margins and return costs. A doubtful low-value prepaid order may not be worth holding; a doubtful high-value COD order usually is.
How much does it cost?
The HLR lookup costs $0.005 per number. The mailbox check and channel checks each have their own real-time and bulk price; see pricing. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). A returning customer who orders again inside the freshness window is answered from your account's cache for free.
To decide whether a check is worth it, compare the price of the checks you run per order with what one failed COD delivery costs you in shipping, handling and lost stock time. Checking only COD and high-value orders keeps the spend where the risk is.
What about consent and compliance?
Buyers give you their phone and e-mail to complete the order, so checking them to deliver it is closely linked to that purpose. Say in your privacy notice that you verify contact details to deliver orders and prevent fraud.
A check is not marketing consent. Delivery updates are transactional; promotional messages need opt-in. Business messages on WhatsApp and similar platforms need the customer's opt-in for that channel. Don't use the checks to find out anything else about a buyer: they never return names or profiles, and reverse lookups are not offered. The acceptable use policy forbids using the results to deny someone credit, housing or employment. People can object through the opt-out form.
What are common mistakes?
- Cancelling on a single weak signal. An unreachable phone may just be switched off. Confirm by another channel first.
- Checking every order the same way. Focus spend on COD, high-value and first orders.
- Checking only after a failed delivery. The value of the check is before dispatch.
- Sending promotions because a messenger account exists. Account presence is not opt-in.
- Blocking unknown results. Unknown means no answer this time. Ship as normal.
- Skipping the shipping country. A national number without a country code can't be checked. Use the shipping country as
default_country, or normalize to E.164 at checkout. - Forgetting guest checkouts. Guest orders often carry the least-tested contact details, and they are where a typo at checkout costs the most.
- Using the checks as identity proof. They describe contact details, not who placed the order. Combine them with your payment and order signals.
Frequently asked questions
Why verify the phone number on an order?
Couriers and support use it to arrange delivery, and for cash on delivery it is often the only way to reach the buyer. An unreachable or unassigned number makes a failed delivery and a return more likely, and you find out only after paying for shipping.
Which orders should I check?
Orders where a bad contact is expensive: cash on delivery, high-value baskets, first orders from new customers, and orders shipped to destinations with high return costs. Low-value prepaid orders from known customers rarely need a check.
Should I cancel an order when the phone check fails?
Not automatically. Ask the customer to confirm or correct the number first, by e-mail or on the order page. Cancel only when the number is conclusively unassigned and the customer doesn't respond.
Can I send delivery updates on WhatsApp if the number has an account?
Only if the customer opted in to updates on that channel. An account is not consent. Without that opt-in, use SMS or e-mail for delivery updates.
Is this a fraud score?
No. The checks return facts about the contact details: reachability, line type, mailbox existence and account presence. They are signals you combine with your own order and payment data. They don't say who the buyer is.
What do the checks cost per order?
An HLR lookup is $0.005 per number. Other checks are priced per check on the pricing page. Unknown answers are free, so you pay only when a check gives a conclusive answer.
When should I run the check, at checkout or before dispatch?
At checkout if you want the customer to fix a typo while they are still on the page. Before dispatch if you want the freshest answer for cash-on-delivery orders. Many shops do the first for all orders and the second only for COD.


