> ## Documentation Index
> Fetch the complete documentation index at: https://docs.boltroute.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive per-email and task completion webhook notifications.

## Overview

Webhooks notify your system when an email verification job completes or when an entire task finishes. Configure a `webhook_url` when creating a batch task or uploading a file. Webhook URLs must be HTTPS.

You can receive two webhook types:

* **Per-email webhook**: sent for each email as it completes (or is skipped).
* **Task completion webhook**: sent once when the entire task finishes.

You can distinguish types by the `data` shape: per-email payloads include `data.job`, while task completion payloads include `data.stats` and `data.jobs`.

## Delivery

Your endpoint receives an HTTP `POST` with JSON. Respond with a `2xx` to acknowledge receipt. We retry on timeouts, connection errors, and `429/502/503/504` responses.

### Headers

Each webhook includes:

* `Content-Type: application/json`
* `User-Agent: EmailVerification/1.0`
* `X-Webhook-ID`: unique UUID per delivery
* `X-Webhook-Timestamp`: Unix seconds when the request is sent
* `X-Webhook-Signature`: HMAC-SHA256 of the raw request body (only when `WEBHOOK_SECRET_KEY` is configured). Format: `sha256=<hex>`

### Signature verification

Compute the HMAC-SHA256 over the **raw** request body using your shared secret and compare to `X-Webhook-Signature`.

```text theme={null}
expected = "sha256=" + hex(hmac_sha256(secret, raw_body))
```

## Payload format (HTTP request body)

All webhooks use the same outer envelope:

```json theme={null}
{
  "event_type": "email_verification_completed",
  "task_id": "550e8400-e29b-41d4-a716-446655440000",
  "completed_at": "2025-01-01T12:05:00Z",
  "data": {}
}
```

Field notes:

* `event_type` is currently always `email_verification_completed`.
* `upload_id` is optional and omitted in most current webhook payloads.
* `completed_at` is when the webhook is sent (UTC).
* `data` depends on webhook type (see below).

## Per-email webhook payload

Sent once per email address. `data` contains the task context and a `job` object for the email.

```json theme={null}
{
  "event_type": "email_verification_completed",
  "task_id": "550e8400-e29b-41d4-a716-446655440000",
  "completed_at": "2025-01-01T12:01:12Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "550e8400-e29b-41d4-a716-446655440999",
    "source": "frontend",
    "created_at": "2025-01-01T12:00:00Z",
    "updated_at": "2025-01-01T12:01:12Z",
    "job": {
      "email_address": "user@example.com",
      "status": "completed",
      "created_at": "2025-01-01T12:00:10Z",
      "updated_at": "2025-01-01T12:01:12Z",
      "email": {
        "id": "550e8400-e29b-41d4-a716-446655440222",
        "status": "valid",
        "address": "user@example.com",
        "local": "user",
        "domain": "example.com",
        "verification_steps": [
          { "step": "syntax", "status": "completed" },
          { "step": "domain", "status": "completed" },
          { "step": "smtp", "status": "completed" }
        ]
      }
    }
  }
}
```

Notes:

* `source` and `user_id` are present when available; they may be omitted in some per-email sends.
* `job.email` can be `null` or omitted if email details are unavailable.
* `job.skipped` is included when an email is skipped.
* `verification_steps` may be empty or omitted if no steps are recorded.

## Task completion webhook payload

Sent once when all jobs for a task finish. `data` contains aggregated stats and the list of jobs.

```json theme={null}
{
  "event_type": "email_verification_completed",
  "task_id": "550e8400-e29b-41d4-a716-446655440000",
  "completed_at": "2025-01-01T12:05:00Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "550e8400-e29b-41d4-a716-446655440999",
    "source": "frontend",
    "created_at": "2025-01-01T12:00:00Z",
    "updated_at": "2025-01-01T12:05:00Z",
    "stats": {
      "total": 2,
      "pending": 0,
      "processing": 0,
      "completed": 2,
      "failed": 0
    },
    "jobs": [
      {
        "email_address": "user@example.com",
        "status": "completed",
        "created_at": "2025-01-01T12:00:10Z",
        "updated_at": "2025-01-01T12:01:12Z",
        "email": {
          "id": "550e8400-e29b-41d4-a716-446655440222",
          "status": "valid",
          "address": "user@example.com",
          "local": "user",
          "domain": "example.com",
          "verification_steps": [
            { "step": "syntax", "status": "completed" },
            { "step": "domain", "status": "completed" },
            { "step": "smtp", "status": "completed" }
          ]
        }
      },
      {
        "email_address": "ops@example.com",
        "status": "completed",
        "created_at": "2025-01-01T12:00:10Z",
        "updated_at": "2025-01-01T12:01:30Z",
        "email": {
          "id": "550e8400-e29b-41d4-a716-446655440223",
          "status": "valid",
          "address": "ops@example.com",
          "local": "ops",
          "domain": "example.com",
          "verification_steps": [
            { "step": "syntax", "status": "completed" },
            { "step": "domain", "status": "completed" },
            { "step": "smtp", "status": "completed" }
          ]
        }
      }
    ]
  }
}
```

Notes:

* `jobs` can be large for big tasks; consider streaming/async processing on your side.
* `verification_steps` may be empty or omitted if no steps are recorded.
