Prices increase in 00D : 00H : 00M Upgrade now Pay less later.

Webhooks

Docs index for AI agents: https://mailvalid.io/llms.txt. Every docs page is also available as Markdown: add .md to its URL.

Instead of polling, get notified when a bulk verification job completes. Pass a webhook_url when you submit a bulk job.

Every webhook includes an X-Mailvalid-Signature header. This is an HMAC-SHA256 of the raw request body, signed with your account's webhook secret. Your secret is shown in the dashboard and is also returned as webhook_secret from GET /api/auth/stats. Recompute the HMAC over the exact bytes you receive and compare. Do not re-serialize the JSON first, or it won't match.

Webhook payload

{
  "event": "job.completed",
  "timestamp": "2026-01-30T10:30:00Z",
  "data": {
    "job_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "total_emails": 100,
    "processed_emails": 100,
    "valid_count": 85,
    "invalid_count": 15,
    "credits_used": 100,
    "started_at": "2026-01-30T10:28:00Z",
    "completed_at": "2026-01-30T10:30:00Z",
    "error_message": null,
    "results_summary": {
      "total": 100,
      "results_url": "https://mailvalid.io/api/v1/verify/bulk/550e8400-e29b-41d4-a716-446655440000"
    }
  }
}

Webhook headers

Header Description
X-Mailvalid-Signature HMAC-SHA256 signature of the payload
X-MailValid-Event Event type: job.completed, job.failed, or job.cancelled
Content-Type application/json

Verifying the signature

Compute an HMAC-SHA256 of the raw request body using your webhook_secret, then compare it to the X-Mailvalid-Signature header. Sign the bytes exactly as you received them. Don't parse and re-serialize the JSON.

import hmac, hashlib
from flask import request, abort

WEBHOOK_SECRET = "your_webhook_secret"  # from GET /api/auth/stats

def handle_webhook():
    raw = request.get_data()  # raw bytes — do NOT use request.json
    expected = hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
    sig = request.headers.get("X-Mailvalid-Signature", "")
    if not hmac.compare_digest(expected, sig):
        abort(401)  # signature mismatch — reject
    event = request.json
    # ... trust event and process ...
    return "", 200

Retries

Failed deliveries are retried with exponential backoff (1, 2, 4, 8 and 16 minutes), up to 5 attempts.

We value your privacy

We use cookies to keep you signed in and, with your permission, to understand how MailValid is used so we can improve it. Choose "Accept All", "Reject All" or pick categories in Cookie Settings. Privacy Policy