Webhooks
Receive real-time HTTP callbacks when events occur in your Kawaa account, such as batch job completion or domain monitoring alerts.
How Webhooks Work
- 1You configure a webhook URL in your Dashboard or via API
- 2An event occurs (e.g., batch job completes)
- 3Kawaa sends an HTTP POST request to your URL with event data
- 4Your server processes the event and responds with 2xx status
Webhook Events
| Event | Description | Available |
|---|---|---|
job.completed | Batch verification job finished | Yes |
job.failed | Batch job failed to complete | Yes |
blacklist.detected | Domain detected on blacklist | Yes |
dmarc.failure | DMARC check failed for monitored domain | Yes |
verification.completed | Single email verification completed (fresh verifications only — cached results return synchronously and emit no event) | Yes |
verification.failed | Single email verification failed before producing a result | Yes |
credits.low | Credit balance fell below 20% of the plan allowance (at most once per 7 days) | Yes |
credits.exhausted | Credit balance reached zero (at most once per 24 hours) | Yes |
Single verifications emit verification.* events; bulk jobs emit job.* events. Query GET /v1/webhooks/events for the current catalog with per-event available flags — only available: true events can be subscribed to.
Webhook Payload
{
"event": "job.completed",
"data": {
"job_id": "9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90",
"status": "completed",
"total_emails": 1000,
"processed_emails": 1000,
"summary": {
"valid": 850,
"invalid": 100,
"risky": 30,
"unknown": 20
},
"created_at": "2026-02-03T14:30:00.000Z",
"completed_at": "2026-02-03T14:35:42.000Z",
"download_url": "https://api.kawaa.com/v1/jobs/9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90/download"
},
"webhook_id": "wh_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
"timestamp": "2026-02-03T14:35:42.100Z"
}Batch jobs submitted with a per-job webhook_url (on POST /v1/verify/batch) receive a flat payload instead: {event, job_id, status, created_at, completed_at, results: {total, processed, valid, invalid, risky, unknown}, download_url}. Those deliveries are signed only when you supply a webhook_secret with the batch request.
Signature Verification
Each webhook request includes a signature header to verify authenticity. Always validate signatures in production.
Headers
X-Kawaa-Event: job.completed
X-Kawaa-Signature: sha256=abc123...
X-Kawaa-Timestamp: 2026-02-03T14:35:42.100Z
X-Kawaa-Id: wh_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6dX-Kawaa-Timestamp is an ISO-8601 timestamp (not a Unix epoch). The signature is an HMAC-SHA256 of the raw request body, computed with your webhook secret and hex-encoded.
Verification (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler — verify against the RAW request body;
// re-serializing a parsed body can change the bytes and break the HMAC.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-kawaa-signature'];
const isValid = verifyWebhookSignature(
req.body, // raw Buffer
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook...
res.status(200).send('OK');
});Retry Policy
Deliveries are attempted up to 3 times within a single delivery run, with a short exponential backoff between attempts and a 10-second timeout per attempt:
- • Only transient failures are retried (network errors, timeouts, HTTP 5xx, and 429)
- • Redirects (3xx) and other 4xx responses are terminal — they are not retried
- • After the final failed attempt the delivery is recorded as failed in your webhook logs
There is no hours-long redelivery schedule, so treat webhooks as best-effort notifications: acknowledge quickly with a 2xx and poll GET /v1/jobs/{job_id} as a fallback if you miss a delivery. Failed registered-webhook deliveries can also be retried manually via POST /v1/webhooks/{id}/logs/{logId}/retry.
Best Practices
Respond Quickly
Return a 2xx response within 10 seconds (the per-attempt timeout). Process the webhook asynchronously if needed.
Handle Duplicates
Webhooks may be delivered multiple times. Deduplicate on the event type plus a stable identifier from the payload (e.g. data.job_id).
Verify Signatures
Always validate the webhook signature to ensure the request is from Kawaa.