Webhooks from Spaw: bulk completion and settled verdicts

updated · September 3, 2026

The two HTTPS callbacks Spaw sends, how to verify the X-Spaw-Signature HMAC in Node or PHP, how retries work, and how to receive them in any automation tool.

Mechanism
Outbound HTTPS POST from Spaw to your URL
Works today
Yes
Auth
X-Spaw-Signature, HMAC-SHA256 of the raw body
Retries
Three, on connection errors and 5xx answers
Cost
None beyond the lookups themselves

Spaw calls you back in two situations: when a queued bulk run finishes, and when a single lookup that first answered unverified settles later. Both are plain HTTPS POSTs with a JSON body and one signature header, so they work with a few lines of code on your server or with the incoming-webhook trigger of any automation tool.

The bulk completion webhook

  1. Create the run with POST https://spaw.co/api/v1/email/bulk, passing emails (up to 100,000) and a webhook_url that speaks HTTPS.
  2. Store the webhook_secret from the response. It is returned at creation, and again only if you replay the same request with the same Idempotency-Key header; otherwise losing it means creating a new run to get a new one.
  3. When the job reaches completed, failed or cancelled, Spaw POSTs to your URL:
{
  "event": "bulk_email_job.finished",
  "job": {
    "id": 512,
    "status": "completed",
    "total": 2,
    "processed": 2,
    "deliverable": 1,
    "risky": 0,
    "undeliverable": 1,
    "credits_used": 1,
    "stopped_reason": null,
    "finished_at": "2026-09-03T10:14:02+00:00"
  }
}
  1. Answer with any 2xx status quickly and do the real work afterwards. A connection error or a 5xx answer is retried three times with a short backoff; the job's webhook_status field then reads delivered or failed.
  2. Fetch the CSV from the results endpoint, optionally filtered with ?variant=deliverable, risky or undeliverable. Results are kept for 30 days.

The settled-verdict callback

Some mail servers will not say whether a mailbox exists on the first try (greylisting, timeouts). Pass callback_url and a callback_secret of 16 to 128 characters on POST /api/v1/email, and an unverified answer is re-checked after 5 and 20 minutes. The response to your original request then carries meta.settling: true, and the callback body is:

{
  "event": "email.settled",
  "request_id": "req_01m1kgdm4xngzmbmff68g94w0c",
  "settled": true,
  "attempt": 1,
  "data": { "email": "[email protected]", "deliverable": "deliverable", "smtp_checked": true },
  "meta": { "credits_used": 0, "cache_hit": true }
}

data is the full 27-field result. settled is false when the last re-check still could not say. Re-checks are repeats of a lookup you already paid for, so they cost nothing.

Verifying the signature

Both callbacks carry X-Spaw-Signature: the lowercase hex HMAC-SHA256 of the exact raw request body, keyed with the secret you were given. Compute it over the bytes as received, before any JSON parsing, and compare in constant time.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function isFromSpaw(rawBody, header, secret) {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
  return header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
$expected = hash_hmac('sha256', $request->getContent(), $secret);
abort_unless(hash_equals($expected, (string) $request->header('X-Spaw-Signature')), 401);

Reject anything that fails the check and log the event and job.id or request_id of what you accept, so a replayed delivery is easy to spot.

Receiving webhooks in an automation tool

Zapier's catch-hook trigger, Make's custom webhook and n8n's Webhook node all accept these POSTs and expose the body's fields to the following steps. Most low-code tools cannot compute an HMAC on the raw body, so keep the receiving URL private and check event plus the job id against the run you created. For a typical flow, the webhook trigger is followed by an HTTP step that downloads the results CSV and a step that writes rows into a sheet or CRM; the n8n page shows the HTTP step.

What to do with the verdict

The completion payload is a summary. Decisions belong to the per-row verdicts in the CSV: deliverable rows go to your mailing tool, risky rows are worth a look at their reason, and undeliverable rows should be removed, or corrected when a did_you_mean column is filled. Undeliverable rows are also added to your account's suppression list automatically, so future batch and bulk runs answer them for free.

Cost

Webhooks and callbacks themselves are free. The bulk run bills each row like a single lookup as it is processed: 1 credit for a fresh deliverable or risky verdict, nothing for undeliverable rows, duplicates, seven-day repeats or suppressed addresses.

Related

markdown version: /integrations/webhooks.md

Verify addresses the same way

The Spaw API runs every check described here on each lookup, with an SMTP handshake that never sends mail. 10 free lookups a month, no card required.

Get your API key

More integrations