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

  1. 1You configure a webhook URL in your Dashboard or via API
  2. 2An event occurs (e.g., batch job completes)
  3. 3Kawaa sends an HTTP POST request to your URL with event data
  4. 4Your server processes the event and responds with 2xx status

Webhook Events

EventDescriptionAvailable
job.completedBatch verification job finishedYes
job.failedBatch job failed to completeYes
blacklist.detectedDomain detected on blacklistYes
dmarc.failureDMARC check failed for monitored domainYes
verification.completedSingle email verification completed (fresh verifications only — cached results return synchronously and emit no event)Yes
verification.failedSingle email verification failed before producing a resultYes
credits.lowCredit balance fell below 20% of the plan allowance (at most once per 7 days)Yes
credits.exhaustedCredit 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

job.completed event
{
  "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_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d

X-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.