Skip to main content
KaijuVerifier
Home Bulk Cleaner Free Checker Pricing Insights Company Log in Get Started Dashboard
Back to Blog

Real-Time Email Verification & Webhooks API Guide

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.

Things to know:
  • Real-time single verify runs inline at the point of capture — block typos, disposables, and invalid addresses before they hit your database.
  • The single-verify API returns a verdict (plus a did-you-mean typo suggestion) in one synchronous request, ideal for signup and lead forms.
  • For large lists, the async bulk jobs API gives you a job_id you can poll — or you can skip polling and receive a job.completed webhook instead.
  • Webhook callbacks are signed with X-Kaiju-Signature: sha256=<HMAC> so you can verify they really came from us.
  • Deliveries retry with exponential backoff; design your handler to be idempotent using the event ID.
  • Choose synchronous batch for small inline lists, async + webhook for big jobs, single verify for one address at a time.

Real-time vs batch verification: when each fits

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.

DimensionReal-time single verifyBatch / bulk
TriggerA user submits a form or rowA scheduled or ad-hoc list cleanup
What mattersLow latency, inline UXThroughput, completeness
Volume per callOne addressThousands to millions
OutcomeAccept / warn / reject at signupSegment and prune an existing list

Validate email on signup: stop bad data at the door

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.

Webhook events: signed callbacks instead of polling

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"
}

Verifying the HMAC-SHA256 signature

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.

Retries, backoff, and idempotency

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.

The async bulk jobs API for large lists

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:

  1. Submit. POST /api/v1/jobs with your list (uploaded or inline). The response returns a job_id and a status of queued.
  2. Track. Either poll GET /api/v1/jobs/{job_id} for status and progress, or register a webhook and do nothing until job.completed arrives.
  3. Fetch. Once complete, call 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.

Which mode should you use?

There are three distinct ways to consume verification, and the right one depends on volume and where the work sits in your stack.

ModeLatencyBest forHow you consume it
Real-time single verifyInline, sub-second targetSignup, lead, and checkout forms — one addressSynchronous response in the same request
Async bulk job + webhookMinutes, runs offlineLarge lists, scheduled re-cleans, CRM syncsSubmit, then receive a job.completed callback
Synchronous batchSeconds, blocks the callSmall inline lists you need answered nowOne 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.

Frequently asked questions

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.