Webhooks
Docs index for AI agents: https://mailvalid.io/llms.txt. Every docs page is also available as Markdown: add
.mdto 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.
Related
- Bounce webhook: send delivery and bounce events back to MailValid