# Webhooks API
Receive signed Klarefi events, send test deliveries, and acknowledge handoff delivery
Source: https://www.klarefi.com/docs/api/webhooks

Klarefi delivers signed events to webhook endpoints registered in the dashboard.
Use webhooks as integration signals, then read the current case or package when
your system needs canonical state.

Subscription deliveries are sent for live-environment events only. Test-mode
events remain available in Klarefi, but they do not fan out to registered
subscriptions. Use `POST /api/v1/webhooks/test` to test a receiver.

> **Use your workspace API base URL**
>
> Set `KLAREFI_API_BASE_URL` to the value in **Settings → Developer** before
> using API examples. Current workspaces may use a
> `https://<deployment>.convex.site` URL.

<Mermaid
  title="Webhook delivery flow"
  chart={`sequenceDiagram
  participant K as Klarefi
  participant R as Your receiver
  participant A as Your app

K->>R: POST signed event
R->>R: verify X-Klarefi-Signature
R->>R: dedupe by delivery_id
R-->>K: 2xx acknowledgement
R->>A: enqueue internal work
A->>K: GET /api/v1/cases/{caseId}
K-->>A: current case record
`}
/>

`v1.webhook.test` is emitted only by the test endpoint. Subscription events use
the catalog names in the dashboard, such as `v1.intake.submitted` and
`v1.case.completed`.

## Event shape

```json
{
  "event_id": "evt_01J7W9S4TSQ0T4SH43JYB9W5R8",
  "delivery_id": "whd_01J7W9S52C2ZK1XFY2GM7Z7SAK",
  "event_type": "v1.case.completed",
  "semantic_event_type": "case.completed",
  "schema_version": "v1",
  "created_at": "2026-05-01T12:34:50.000Z",
  "occurred_at": "2026-05-01T12:34:50.000Z",
  "org_id": "org_abc123",
  "case_id": "case_abc123",
  "session_id": null,
  "external_case_id": "claim_12345",
  "external_applicant_id": "applicant_789",
  "external_customer_id": "customer_456",
  "idempotency_key": "session_claim_12345",
  "trace_id": "trace_claim_12345",
  "data": {
    "case_status": "completed"
  }
}
```

Deliveries are at least once. Process duplicates idempotently by `delivery_id`.
`created_at` equals `occurred_at`; both fields record when the domain event
occurred.

## Retry schedule

Klarefi makes up to 5 delivery attempts. After a failed attempt, retries are
scheduled after 60 seconds, 5 minutes, 30 minutes, and 2 hours.

After the final failed attempt, the delivery is marked `failed`. Klarefi emits
`webhook.delivery_failed`, delivered as catalog event
`v1.webhook.delivery_failed` for matching webhook subscriptions, unless the
failed delivery was already a delivery-failed notification. Klarefi also creates
an integration notification. If a `case.completed` handoff delivery exhausts
its attempts, the case handoff state is marked dispatch failed.

## Signature verification

Each delivery includes:

```http
X-Klarefi-Signature: t=1704067200,v1=abc123def456...
X-Klarefi-Event: v1.case.completed
X-Klarefi-Event-Id: evt_01J7W9S4TSQ0T4SH43JYB9W5R8
X-Klarefi-Delivery: whd_01J7W9S52C2ZK1XFY2GM7Z7SAK
```

`X-Klarefi-Event` carries the catalog event name, `X-Klarefi-Event-Id`
identifies the domain event, and `X-Klarefi-Delivery` identifies the delivery
attempt series.

The signature is:

```text
HMAC-SHA256(signing_secret, timestamp + "." + raw_body)
```

Use the raw request body before JSON parsing.

Install the SDK:

```bash
npm install @klarefi/node
```

```ts
import { constructEvent, KlarefiWebhookSignatureError } from "@klarefi/node";

export async function POST(request: Request) {
  const rawBody = await request.text();

  try {
    const event = constructEvent(
      rawBody,
      request.headers.get("X-Klarefi-Signature"),
      process.env.KLAREFI_WEBHOOK_SECRET!,
    );

    if (event.event_type === "v1.case.completed") {
      // Read the case package and update your system of record.
    }

    return Response.json({ received: true });
  } catch (error) {
    if (error instanceof KlarefiWebhookSignatureError) {
      return new Response("Invalid signature", { status: 401 });
    }
    throw error;
  }
}
```

## Test a webhook receiver

Required scope: `webhooks:test`

```bash
npx klarefi webhooks test \
  --endpoint-url https://your-app.example.com/webhooks/klarefi \
  --signing-secret whsec_your_signing_secret
```

Use this command when you already have `KLAREFI_API_KEY` configured. The HTTP
equivalent is:

```bash
curl -X POST "$KLAREFI_API_BASE_URL/api/v1/webhooks/test" \
  -H "Authorization: Bearer $KLAREFI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint_url": "https://your-app.example.com/webhooks/klarefi",
    "signing_secret": "whsec_your_signing_secret"
  }'
```

```json
{
  "success": true,
  "status_code": 200
}
```

To verify a captured payload locally:

```bash
npx klarefi webhooks verify \
  --payload payload.json \
  --signature "t=1704067200,v1=..." \
  --secret whsec_your_signing_secret
```

## Acknowledge a delivery

Required scope: `webhooks:acknowledge`

With an existing `Klarefi` client:

```ts
await klarefi.webhooks.acknowledge("whd_123", {
  acknowledgement_id: "ack_claim_12345",
});
```

```bash
curl -X POST "$KLAREFI_API_BASE_URL/api/v1/webhooks/deliveries/whd_123/ack" \
  -H "Authorization: Bearer $KLAREFI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "acknowledgement_id": "ack_claim_12345" }'
```

```json
{
  "acknowledged": true,
  "already_acknowledged": false,
  "delivery_id": "whd_123",
  "case_id": "case_abc123"
}
```

Acknowledgement is useful when your system wants Klarefi to record that a
decision-ready handoff has been received.
