Published on June 15, 2026 • By Kaiju Team
Real-time email verification validates an address the instant it enters your system — at the signup form, the lead form, or checkout — instead of in a nightly batch after the bad data is already in your database. This guide covers when to verify in real time versus in bulk, how to validate on signup with a single-verify API, and how to wire up an email verification webhook so KaijuVerifier POSTs signed callbacks to your endpoint when long-running jobs finish — no polling required.
job_id you can poll — or you can skip polling and receive a job.completed webhook instead.X-Kaiju-Signature: sha256=<HMAC> so you can verify they really came from us.The two modes solve different problems. Real-time verification answers "should I accept this one address right now?" and runs inline in a user-facing request, so latency matters — you want a verdict back in the time it takes to validate a form field. Batch verification answers "how clean is this list of 200,000 contacts I already have?" and runs offline, where throughput matters more than per-address latency.
A healthy data pipeline uses both. You verify at the door to keep junk out, and you periodically re-clean what you already stored, because mailboxes that were valid last quarter go dark, employees change jobs, and domains lapse. Real-time keeps new data clean; bulk keeps old data fresh. If you only do one, the other source of decay wins eventually.
| Dimension | Real-time single verify | Batch / bulk |
|---|---|---|
| Trigger | A user submits a form or row | A scheduled or ad-hoc list cleanup |
| What matters | Low latency, inline UX | Throughput, completeness |
| Volume per call | One address | Thousands to millions |
| Outcome | Accept / warn / reject at signup | Segment and prune an existing list |
The cheapest place to fix a bad email is before it is ever written to your database. Once an invalid address is stored, it propagates: it triggers a hard bounce in your next campaign, drags down your sender reputation, skews your activation metrics, and someone eventually has to clean it up by hand. Verifying at the point of capture short-circuits all of that.
When a user types their email into a signup or lead form, fire a single request to the verify endpoint as they blur the field or on submit. KaijuVerifier checks syntax, resolves MX records, runs an SMTP probe, and flags disposable, role-based, and catch-all addresses. Crucially, it also returns a did-you-mean typo suggestion: when someone types jane@gmial.com or bob@hotmial.com, the response includes a corrected candidate you can surface as "Did you mean jane@gmail.com?" — recovering a real signup that would otherwise bounce or be lost entirely.
A minimal single-verify call looks like this:
POST /api/v1/verify
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{ "email": "jane@gmial.com" }
200 OK
{
"email": "jane@gmial.com",
"result": "undeliverable",
"reason": "domain_typo",
"did_you_mean": "jane@gmail.com",
"disposable": false,
"role": false,
"catch_all": false
}
From there your form logic decides policy: hard-reject undeliverable and disposable addresses, accept a did_you_mean correction with one click, and let risky or catch-all verdicts through with a soft warning rather than a hard block. Try the interactive single email validator to see the verdicts, or read the full request and response shapes in the API documentation. For a deeper look at why typo and disposable filtering protects your domain, see our guide to reducing bounce rate.
Single verifies return synchronously, but bulk jobs do not — a list of 500,000 addresses takes minutes, not milliseconds. Rather than hammer a status endpoint waiting for it to finish, you can register a webhook and let KaijuVerifier push a notification to you the moment an event occurs. Typical events include batch.completed and job.completed; the payload carries the job ID, final counts, and a URL to fetch results.
Because the callback hits a public endpoint, you must confirm it actually came from us and not an attacker spraying your URL. Every delivery includes an HMAC signature header:
POST /your/webhook/endpoint
X-Kaiju-Signature: sha256=4f1d8c...e9
X-Kaiju-Event: job.completed
Content-Type: application/json
{
"event": "job.completed",
"id": "evt_9f2a7c1b",
"job_id": "job_a1b2c3",
"total": 48211,
"deliverable": 41980,
"undeliverable": 5102,
"risky": 1129,
"results_url": "https://kaijuverifier.com/api/v1/jobs/job_a1b2c3/results"
}
The signature is an HMAC-SHA256 computed over the raw request body using your webhook signing secret as the key. To verify, recompute the HMAC on your side and compare it to the value in the header. Compare with a constant-time function so you do not leak timing information. Here is the logic in pseudocode:
function verify(rawBody, signatureHeader, signingSecret):
# header looks like: "sha256=4f1d8c...e9"
expected = hmac_sha256(key = signingSecret, message = rawBody)
received = signatureHeader.removePrefix("sha256=")
return constant_time_equals(toHex(expected), received)
A few rules that save you debugging time. Hash the raw bytes of the body, not a re-serialized JSON object — frameworks that parse and re-encode JSON will reorder keys and change whitespace, breaking the comparison. Many frameworks expose the raw body explicitly (for example, a rawBody buffer) precisely for signature checks. Reject any request where the signature is missing or does not match, and keep your signing secret in environment config, never in client-side code.
Networks fail and endpoints have bad deploys, so webhook delivery is best-effort with retries. If your endpoint does not return a fast 2xx — typically because it errored, timed out, or returned a 5xx — the delivery is retried on an exponential backoff schedule (the gap between attempts grows: seconds, then minutes, then longer). Acknowledge first and process afterward: return 200 quickly, then do the heavy work (downloading results, updating your CRM) in a background job. Endpoints that do expensive work synchronously time out and trigger needless retries.
Retries mean the same event can legitimately arrive more than once, so your handler must be idempotent. Every event carries a stable id (for example evt_9f2a7c1b). Record processed IDs and skip duplicates:
on_webhook(req):
if not verify(req.rawBody, req.headers["X-Kaiju-Signature"], SECRET):
return 401
event = parse(req.rawBody)
if already_processed(event.id): # idempotency guard
return 200 # ack the duplicate, do nothing
enqueue_background_job(event) # do the work async
mark_processed(event.id)
return 200
This combination — verify the signature, dedupe on event ID, ack fast, process async — is the standard, robust pattern for consuming any signed webhook, and it keeps your integration stable even when a downstream system is briefly down.
For lists too big to verify in a single synchronous request, use the async jobs flow. You submit the list, get a job_id back immediately, and then either poll for status or — better — wait for a job.completed webhook. The lifecycle is:
POST /api/v1/jobs with your list (uploaded or inline). The response returns a job_id and a status of queued.GET /api/v1/jobs/{job_id} for status and progress, or register a webhook and do nothing until job.completed arrives.GET /api/v1/jobs/{job_id}/results (the same URL the webhook hands you) to download per-address verdicts.POST /api/v1/jobs -> { "job_id": "job_a1b2c3", "status": "queued" }
GET /api/v1/jobs/job_a1b2c3 -> { "status": "processing", "progress": 0.62 }
GET /api/v1/jobs/job_a1b2c3 -> { "status": "completed" }
GET /api/v1/jobs/job_a1b2c3/results -> [ { "email": "...", "result": "deliverable" }, ... ]
Polling is simple to build but wasteful — most requests come back "still processing," and you pay in latency and request volume. Webhooks invert that: you receive exactly one notification at exactly the right moment. If you would rather not write any code at all for occasional cleanups, the bulk email cleaner runs the same engine through a UI where you upload a CSV and download the scrubbed list. For ongoing programmatic use, the developer docs walk through the full jobs API end to end.
There are three distinct ways to consume verification, and the right one depends on volume and where the work sits in your stack.
| Mode | Latency | Best for | How you consume it |
|---|---|---|---|
| Real-time single verify | Inline, sub-second target | Signup, lead, and checkout forms — one address | Synchronous response in the same request |
| Async bulk job + webhook | Minutes, runs offline | Large lists, scheduled re-cleans, CRM syncs | Submit, then receive a job.completed callback |
| Synchronous batch | Seconds, blocks the call | Small inline lists you need answered now | One request in, all verdicts back in the response |
A common production setup uses all three: single verify on the public signup form, synchronous batch for a sales rep pasting in 50 addresses, and async jobs with webhooks for the nightly database hygiene run. If you are evaluating volume tiers, the pricing page lays out where free turns into paid, and our broader notes on email deliverability best practices cover how verification fits into the wider picture of reaching the inbox.
What is the difference between real-time and batch email verification?
Real-time verification validates one address inline, the instant a user submits it, so latency matters and you act on the verdict immediately — for example blocking a typo at signup. Batch verification processes an existing list offline, where throughput and completeness matter more than per-address speed. Most teams use both: real-time to keep new data clean, batch to keep stored data fresh.
How do I verify a KaijuVerifier webhook signature?
Compute an HMAC-SHA256 over the raw request body using your webhook signing secret, hex-encode it, and compare it — in constant time — against the value in the X-Kaiju-Signature: sha256=... header. Hash the raw bytes, not a re-serialized JSON object, or key reordering will break the match. Reject any request whose signature is missing or does not match.
Should I poll for job status or use a webhook?
Use a webhook when you can. Polling repeatedly asks "are you done yet?" and wastes requests and latency, while a job.completed webhook delivers exactly one notification at the right moment. Polling is still fine for quick scripts or environments where you cannot expose a public endpoint to receive callbacks.
What happens if my webhook endpoint is down?
Deliveries that do not receive a fast 2xx are retried on an exponential backoff schedule, so a brief outage will not lose the event. Because retries can deliver the same event more than once, make your handler idempotent by recording each event's id and skipping duplicates.