Key Takeaways

  • For large lists, submit a batch, receive a job ID immediately, and get a webhook when processing finishes, instead of holding a connection open.
  • Webhooks beat polling: the callback fires the moment the job completes, with no repeated status requests adding latency and cost.
  • Always verify the HMAC signature on the webhook against the exact raw request body, before any JSON parsing, using your webhook secret.
  • Keep a polling fallback. If a webhook never arrives, you can still recover the result by querying the job status endpoint.

Verifying a handful of addresses is a synchronous call: you ask, you wait a second, you get an answer. Verifying hundreds of thousands is not, because the job can take minutes to hours and no sensible system holds an HTTP connection open that long. Async bulk email verification with webhooks is the pattern that handles this cleanly: submit the batch, get a job ID, and let the service push you a notification when it is done. This guide builds the full flow, including the signature verification that keeps the callback secure. For the single-address and synchronous bulk patterns, see the email verification integrations hub.

The shape of the pattern is three steps: submit, process, callback. The client never blocks waiting for the result.

Step 1: Submit the Batch

You POST the list of addresses along with a webhook_url that the service will call when the job finishes. The response comes back immediately with a job ID and a queued status, using the HTTP 202 Accepted convention that signals the work was accepted but is not done yet.

curl -X POST "https://emailverifierapi.com/v2/batch" 
  -H "Content-Type: application/json" 
  -d '{
    "api_key": "YOUR_API_KEY",
    "webhook_url": "https://yourapp.com/hooks/verify",
    "emails": ["a@acme.com", "b@beta.io", "c@gamma.co"]
  }'

# 202 Accepted
{ "job_id": "job_8fK2x", "status": "queued", "total": 3 }

You store the job_id against whatever context you will need later (which list, which user, which campaign), because the webhook will reference it and you must match the callback back to the right place. This correlation is the hard part of any async API, so persist the mapping before you expect the callback.

Step 2: The Receiver and Signature Check

When the job finishes, the service POSTs a batch.completed event to your webhook_url, signed with an HMAC using the webhook secret from your dashboard. The single most important rule is to verify that signature against the exact raw request body, before parsing the JSON, because re-serializing the parsed object changes the bytes and breaks the comparison.

// Node.js / Express receiver
import express from "express";
import crypto from "crypto";

const app = express();
const SECRET = process.env.EMAILVERIFIER_WEBHOOK_SECRET;

// Capture the RAW body; do not let a JSON parser touch it first
app.use("/hooks/verify", express.raw({ type: "*/*" }));

app.post("/hooks/verify", (req, res) => {
  const sig = req.header("X-EmailVerifier-Signature") || "";
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(req.body)              // req.body is a Buffer: the raw bytes
    .digest("hex");

  // Constant-time compare to avoid timing attacks
  const ok = sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  if (!ok) return res.status(401).send("bad signature");

  const event = JSON.parse(req.body.toString("utf8"));
  if (event.type === "batch.completed") {
    // ACK fast, then process out of band
    queueResultFetch(event.job_id);
  }
  res.status(200).send("ok");     // return 200 quickly
});

Three things make this correct. The raw body is preserved so the HMAC matches. The comparison is constant-time to avoid leaking information through timing. And the handler returns 200 immediately and does the real work out of band, because a webhook delivery is considered successful only when your endpoint returns 200 fast; slow handlers get retried or marked failed.

Verify the HMAC against the exact raw body, never the re-serialized JSON. Source: 2026 webhook security best practice

Step 3: Fetch the Results

The completion webhook tells you the job is done, not necessarily the full results inline. Fetch them from the job endpoint, then route each verdict the same way you would any verification result: send the valid addresses, suppress the invalid, review the risky.

async function queueResultFetch(jobId) {
  const url = `https://emailverifierapi.com/v2/batch/${jobId}` +
              `?api_key=${process.env.EMAILVERIFIER_API_KEY}`;
  const res = await fetch(url);
  const { status, results } = await res.json();
  if (status !== "completed") return;        // not ready: let fallback retry

  for (const r of results) {
    if (r.status === "passed" && !r.isRoleAccount) accept(r.email);
    else if (r.status === "failed")             suppress(r.email);
    else                                        review(r.email);
  }
}

Keep a Polling Fallback

Webhooks are strictly better than polling for latency and cost, but they can be missed: a deploy restarts your receiver, a network blip drops the delivery, or your endpoint was briefly down. Always keep a polling fallback so you can recover. Schedule a job that checks the status endpoint for any batch that has not reported a completed webhook within an expected window.

The combination gives you the best of both: the webhook delivers results the instant the job finishes in the normal case, and the poller guarantees you never lose a result when a callback goes missing. New developers can test the full submit-and-callback flow with 100 free email verification credits, and the email verification API documentation covers the batch endpoints, the webhook payload, and the signature scheme in full.

Best Practice Make your receiver idempotent. A webhook can be delivered more than once, so processing the same batch.completed event twice should not double-apply results. Key your processing on the job_id and skip any job you have already handled.

Frequently Asked Questions

When should I use webhooks instead of polling for verification?

Use webhooks for bulk jobs that take more than a few seconds, which is any large list. Polling works but adds operational noise and cost because you make repeated status requests. A webhook fires once, the moment the job completes, so it is both faster and cheaper. Keep polling only as a fallback for missed callbacks.

How do I verify a verification webhook is authentic?

Compute an HMAC of the exact raw request body using your webhook secret, and compare it in constant time to the signature header. Do this before parsing the JSON, because re-serializing the parsed object changes the bytes and breaks the comparison. Reject any request whose signature does not match.

What happens if my webhook endpoint is down when the job finishes?

The delivery may be retried by the service, but you should not rely on that alone. Keep a polling fallback that checks the batch status endpoint for any job that has not reported a completed webhook within an expected window, so you can always recover the results even when a callback is missed.

Why should my webhook handler return 200 immediately?

Because a delivery is considered successful only when your endpoint returns 200 quickly. If your handler does slow work before responding, the delivery can time out and be marked failed or retried, causing duplicates. Acknowledge with 200 right away, then fetch results and process them out of band in a queue.