Glasser Team8 min read
Email Verification API Examples in Node.js and PHP
Implement an email verification API in Node.js or PHP. Handle pending requests, uncertain results and service failures without losing contact data.

What does this email verification API example check?
An email verification API takes an address you already have and returns evidence about its deliverability. This guide implements a small server-side client in Node.js and PHP, then keeps mailbox verdicts separate from pending work and service failures.
Use this flow when importing contacts or checking an address submitted through a form. If you need to prove that a user controls the inbox, add a separate confirmation-link or code flow. A deliverability check cannot establish ownership.
The examples use Hunter's direct Email Verifier API. Its public documentation was checked on October 9, 2026. The Node.js client passed offline response tests; the PHP version received static review only. Neither example was sent to the live service.
What do you need before making a request?
Choose the access path for your application
Glasser's Hunter verification operation accepts an existing email address. If you already use Glasser for lead data, inspect that operation's current input and charging terms before introducing another provider account. The public catalog does not publish a guaranteed output schema, so confirm the returned structure before mapping fields.
The code below is for direct Hunter access. Prepare a Hunter API key, keep it in the server environment as HUNTER_API_KEY, and supply an address you are authorized to check as EMAIL_TO_CHECK. Use Node.js 22 or later, or PHP 8.2 or later with the cURL extension. These are the runtime assumptions for these examples.
Keep the request on the server
The client calls GET /v2/email-verifier with an encoded email parameter and sends the credential in the documented X-API-KEY header. Keep credentials out of browser JavaScript and logs. Avoid logging full query URLs because they contain the address being checked. Hunter authentication and verifier documentation
Hunter documents a verification wait of up to 20 seconds before returning HTTP 202. The examples allow 30 seconds for one request and return a pending state immediately on 202. Any later polling belongs in your application, with a bounded attempt count and delay.
How do you call the API from Node.js and PHP?
Save the Node.js client
Save this as verify-email.mjs. The optional fetcher argument allows offline response testing; normal calls use Node's built-in fetch.
export async function verifyEmail(email, apiKey, fetcher = fetch) {
if (!email?.trim() || !apiKey?.trim()) {
throw new Error('Email and HUNTER_API_KEY are required');
}
const url = new URL('https://api.hunter.io/v2/email-verifier');
url.searchParams.set('email', email.trim());
const signal = AbortSignal.timeout(30_000);
let response;
try {
response = await fetcher(url, {
headers: { 'X-API-KEY': apiKey, Accept: 'application/json' },
signal,
redirect: 'error',
});
} catch {
throw new Error(signal.aborted ? 'Verification timed out' : 'Verification transport failed');
}
if (response.status === 202) return { state: 'pending' };
if (response.status !== 200) {
throw new Error(`Verification HTTP ${response.status}`);
}
let payload;
try {
payload = await response.json();
} catch {
throw new Error(signal.aborted ? 'Verification timed out' : 'Unreadable verification response');
}
const statuses = ['valid', 'invalid', 'accept_all', 'webmail', 'disposable', 'unknown'];
if (!statuses.includes(payload?.data?.status)) {
throw new Error('Unexpected verification schema');
}
return { state: 'complete', status: payload.data.status, raw: payload.data };
}
In another file, check-email.mjs, import the function and report only the application state and verdict:
import { verifyEmail } from './verify-email.mjs';
try {
const result = await verifyEmail(
process.env.EMAIL_TO_CHECK,
process.env.HUNTER_API_KEY,
);
console.log({ state: result.state, status: result.status ?? null });
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
After setting the two environment variables privately, run node check-email.mjs. A pending result leaves the verdict empty. Schedule a later call with the same address if your workflow needs to finish the check.
Save the PHP client
Save this as verify-email.php. It applies the same response rules using PHP's cURL and JSON support.
<?php
function verifyEmail(string $email, string $apiKey): array {
if (trim($email) === '' || trim($apiKey) === '') {
throw new RuntimeException('Email and HUNTER_API_KEY are required');
}
$url = 'https://api.hunter.io/v2/email-verifier?' . http_build_query(
['email' => trim($email)], '', '&', PHP_QUERY_RFC3986
);
$ch = curl_init($url);
if ($ch === false) throw new RuntimeException('Cannot initialize cURL');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-KEY: ' . $apiKey, 'Accept: application/json'],
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => false,
]);
$body = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$error = curl_errno($ch);
curl_close($ch);
if ($body === false) {
throw new RuntimeException($error === CURLE_OPERATION_TIMEDOUT
? 'Verification timed out' : 'Verification transport failed');
}
if ($code === 202) return ['state' => 'pending'];
if ($code !== 200) throw new RuntimeException('Verification HTTP ' . $code);
try {
$payload = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new RuntimeException('Unreadable verification response');
}
$data = is_array($payload) ? ($payload['data'] ?? null) : null;
$status = is_array($data) ? ($data['status'] ?? null) : null;
$statuses = ['valid', 'invalid', 'accept_all', 'webmail', 'disposable', 'unknown'];
if (!in_array($status, $statuses, true)) {
throw new RuntimeException('Unexpected verification schema');
}
return ['state' => 'complete', 'status' => $status, 'raw' => $data];
}
Call it from check-email.php:
<?php
require __DIR__ . '/verify-email.php';
try {
$result = verifyEmail(
getenv('EMAIL_TO_CHECK') ?: '',
getenv('HUNTER_API_KEY') ?: ''
);
echo json_encode([
'state' => $result['state'],
'status' => $result['status'] ?? null,
], JSON_THROW_ON_ERROR) . PHP_EOL;
} catch (RuntimeException $e) {
fwrite(STDERR, $e->getMessage() . PHP_EOL);
exit(1);
}
With the environment variables set, run php check-email.php. Both clients accept HTTP 200 as a completed response and explicitly handle 202 before decoding. This prevents a generic “any 2xx is success” check from misreading Hunter's special 222 failure response.
How should your application handle verification results?
Preserve the provider verdict before applying a policy
Hunter's current field is data.status; its older result field is deprecated. The wrapper's state is our own application label. A completed request can still return an uncertain mailbox verdict.
| Provider status | Suggested application treatment |
|---|---|
valid | Retain the positive verdict and check date; apply existing contact preferences separately |
invalid | Flag the address for correction or exclusion under your address-quality rules |
accept_all | Hold for review when you need mailbox-specific confidence |
webmail | Apply your policy for personal email services; avoid automatically treating the category as invalid |
disposable | Apply your explicit disposable-address policy |
unknown | Preserve uncertainty and choose whether a later check is useful |
The statuses come from Hunter's verifier reference. The actions in the right column are editorial recommendations for your application. The code returns raw data so you can retain additional evidence without inventing a universal acceptance threshold.
Keep request time and verification time separate
Store the address, provider, request time, raw status, application decision, and provider verification date when supplied. Hunter can return a prior verification, so the request timestamp alone does not show when the mailbox was checked. Preserve missing dates as missing.
For form submissions, decide how users proceed when verification is pending or unavailable. For imports, keep unresolved rows in a review queue instead of silently discarding them. Never replace an existing address-quality decision with a network error.
How do you keep failures out of your contact decisions?
Diagnose the request before changing the record
| Observation | Next action |
|---|---|
HTTP 202 | Keep pending; schedule a bounded later poll of the same endpoint |
HTTP 222 | Keep the SMTP-check failure separate; consider a later retry |
HTTP 400 | Review the supplied address and request parameters |
HTTP 401 | Resolve credential access |
HTTP 403 or 429 | Check the provider's rate or usage limit and account state |
HTTP 451 | Stop processing this address and apply the provider's restriction |
HTTP 5xx, timeout or network failure | Preserve the existing contact decision and investigate service availability |
| Invalid JSON or unexpected fields | Treat the response as an integration problem and review the current schema |
Hunter documents these HTTP conditions in its API reference. The clients deliberately avoid automatic retries. Add retry scheduling only after choosing a request budget, a maximum number of attempts and a policy for terminal errors.
If provider authentication and billing are the main integration burden, inspect Glasser's verification contract. Glasser documents one key and a Workspace balance for supported operations. Keep its execution status, returned data and charge handling aligned with the Glasser workflow documentation; the direct Hunter field mapping above needs separate validation before reuse.
Test decisions without sending real addresses
The accompanying Node.js client passed 23 offline tests covering all six verdicts, pending responses, HTTP errors, malformed payloads, plus-address encoding, missing credentials, transport failures and an injected aborted signal. The timeout test exercises the error branch without waiting 30 seconds. These fixtures establish client behavior under supplied responses; they do not measure API availability or verification accuracy.
The PHP client has not been executed because the review environment has no PHP runtime. Before production use, run it under your deployed PHP version, exercise the same failure cases, and perform an authorized service check with your own account. Also add application-level authentication, request limits and queue handling appropriate to your workload.
Frequently asked questions
Does this send a confirmation email?
These examples request a deliverability check. Use a separate confirmation message and token flow when the user must demonstrate access to an inbox.
Can I reject every unknown result?
An unknown verdict leaves the check unresolved. Decide whether to permit, review or recheck it based on your application policy, and retain that uncertainty in the record.
Can I paste this code into a browser form?
Keep the provider credential on your server. Have the form call your own authenticated and rate-limited backend rather than exposing the API key to visitors.
Can I reuse the same response mapping through Glasser?
Inspect the selected Glasser operation and an authorized sample result first. The direct API's data.status path is not a verified Glasser response contract.
Where can I compare free verification allowances?
The free email verification API guide covers provider selection and allowances. This implementation assumes you have chosen and configured your access.
Related posts
View more
8 min read
Free Email Verification API: Compare Options and Handle Every Result
Compare free email verification API limits and costs. Learn how to handle valid, invalid, catch-all, and unknown results in signup forms and list cleaning.

13 min read
Lead Enrichment: How to Enrich B2B Leads and Choose the Right Tools
Turn incomplete B2B leads into usable CRM records. Compare enrichment tools, resolve conflicting data, and calculate cost per usable lead.
Make your agent
work with real data.
Search, inspect, and run data APIs through one Glasser key. Turn your next question into a result you can use.
Get started with Glasser