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

# Webhooks

Kernel can send HTTP callbacks (webhooks) when jobs complete or fail. This lets you receive results without polling.

## Enabling webhooks

Include a `webhook_url` field when creating a job. All async endpoints — entity resolution, firmographic enrichment, and resolve parent — support webhooks:

**Entity resolution:**

```bash curl theme={null}
curl -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"legal_name": "Stripe", "webhook_url": "https://example.com/webhooks/kernel"}'
```

**Firmographic enrichment** (via the service API):

```bash curl theme={null}
curl -X POST https://api.kernel.ai/rest/v1/firmographics \
  -H "x-api-key: $KERNEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kernel_id": "6347422643", "webhook_url": "https://example.com/webhooks/kernel"}'
```

**Resolve parent:**

```bash curl theme={null}
curl -X POST https://api.kernel.ai/rest/v1/resolve-parent \
  -H "x-api-key: $KERNEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kernel_id": "6347422643", "webhook_url": "https://example.com/webhooks/kernel"}'
```

The URL must be an HTTPS URL that accepts POST requests.

## Webhook payload

When a job completes or fails, Kernel sends a POST request to your `webhook_url` with the following JSON body:

```json theme={null}
{
  "event": "entity-resolution.completed",
  "timestamp": "2026-06-08T15:30:00Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "created_at": "2026-06-08T15:00:00Z",
    "completed_at": "2026-06-08T15:00:32Z",
    "record": { ... }
  }
}
```

`data` matches the result you get when polling the job's GET endpoint. If you included an `external_id` in the original request, it is echoed back on `data.record.external_id` so you can correlate the callback with the record you submitted.

For failed jobs, `status` is `"failed"`, `record` is `null`, and `error_message` with `error_type` are included:

```json theme={null}
{
  "event": "entity-resolution.failed",
  "timestamp": "2026-06-08T15:31:00Z",
  "data": {
    "jobid": "550e8400-e29b-41d4-a716-446655440000",
    "status": "failed",
    "created_at": "2026-06-08T15:00:00Z",
    "completed_at": "2026-06-08T15:00:32Z",
    "record": null,
    "error_message": "Entity not found",
    "error_type": "kernel_id_not_found"
  }
}
```

The `error_message` field is sanitized — internal details like stack traces, UUIDs, and kernel IDs are stripped before delivery.

## Event types

| Event                               | Trigger                                             |
| ----------------------------------- | --------------------------------------------------- |
| `entity-resolution.completed`       | An entity resolution job finished successfully      |
| `entity-resolution.failed`          | An entity resolution job failed                     |
| `firmographic-enrichment.completed` | A firmographic enrichment job finished successfully |
| `firmographic-enrichment.failed`    | A firmographic enrichment job failed                |
| `resolve-parent.completed`          | A resolve-parent job finished successfully          |
| `resolve-parent.failed`             | A resolve-parent job failed                         |

## Request headers

| Header               | Description                                                                           |
| -------------------- | ------------------------------------------------------------------------------------- |
| `Content-Type`       | `application/json`                                                                    |
| `X-Kernel-Signature` | `sha256=<HMAC-SHA256 hex digest>` — verify this to confirm the payload is from Kernel |
| `X-Kernel-Event`     | The event type (e.g. `entity-resolution.completed`)                                   |
| `X-Kernel-Delivery`  | A unique ID for this delivery attempt                                                 |

## Verifying signatures

Your signing secret is generated when you create an API key and is shown once. To verify a webhook:

1. Compute the HMAC-SHA256 of the raw request body using your signing secret
2. Compare the result (as hex) to the value in the `X-Kernel-Signature` header (after removing the `sha256=` prefix)
3. Use a constant-time comparison to prevent timing attacks

```python theme={null}
import hmac, hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature.removeprefix("sha256="))
```

## Retry policy

If your endpoint returns a non-2xx status or times out (5 second limit), Kernel retries up to 3 times with exponential backoff:

| Attempt | Delay      |
| ------- | ---------- |
| 1       | Immediate  |
| 2       | 30 seconds |
| 3       | 2 minutes  |

After 3 failed attempts, the delivery is marked as permanently failed.
