Webhook Setup
Receive signed Klarefi events and verify webhook signatures
Overview
Klarefi sends signed, versioned domain events for intake, case, review,
document, and webhook delivery activity. Webhooks are useful in both
system_of_action and system_of_intelligence integrations.
All webhook deliveries are sent to registered endpoints and signed with HMAC-SHA256. Per-request webhook URLs are rejected; configure endpoint URLs in the dashboard so delivery policy, retries, and signing secrets stay consistent.
Setting Up Webhooks
1. Register an Endpoint
Register a webhook endpoint in the Klarefi dashboard under Webhooks. You'll
receive an endpoint signing secret (whsec_...) to verify incoming events.
Secrets are scoped to a registered endpoint and are shown only once.
2. Implement Your Receiver
Your endpoint must:
- Accept POST requests with JSON body
- Return a 2xx status code within 30 seconds
- Verify the signature before processing
- Process duplicate deliveries idempotently using the stable
delivery_id
Signature Verification
Every webhook includes an X-Klarefi-Signature header:
X-Klarefi-Signature: t=1704067200,v1=abc123def456...The signature is computed as: HMAC-SHA256(signing_secret, timestamp + "." + raw_body)
Event Payloads
Events use a versioned name and carry schema metadata. The event name is stable within a version; additive fields may appear over time, so ignore unknown keys.
{
"event_id": "evt_01J7W9S4TSQ0T4SH43JYB9W5R8",
"delivery_id": "whd_01J7W9S52C2ZK1XFY2GM7Z7SAK",
"event_type": "v1.case.completed",
"schema_version": "v1",
"trace_id": "trc_01J7W9S4V8ZKH4W4B8EG9J7TKC",
"created_at": "2026-05-01T12:34:56.000Z",
"data": {
"case_status": "complete"
}
}delivery_id is stable for a delivery attempt series and should be used for
idempotency. event_id identifies the domain event. trace_id can be used when
correlating dashboard delivery logs with receiver logs.
Event Catalog
Webhook endpoints can subscribe to individual event types or * for the full
MVP catalog:
v1.intake.session_createdv1.intake.submittedv1.case.processingv1.case.applicant_blockedv1.case.needs_reviewv1.case.completedv1.case.failedv1.review.completedv1.document.processedv1.document.failedv1.intake.gap_resolvedv1.webhook.delivery_failed*
TypeScript
import { verifyWebhookSignature } from "@klarefi/sdk";
import express from "express";
const app = express();
app.post(
"/webhooks/klarefi",
express.raw({ type: "application/json" }),
async (req, res) => {
try {
const event = await verifyWebhookSignature(
req.body.toString(),
req.headers["x-klarefi-signature"] as string,
"whsec_your_signing_secret",
);
console.log("Received event:", event.event_type, event.delivery_id);
res.sendStatus(200);
} catch (err) {
console.error("Webhook verification failed:", err);
res.sendStatus(401);
}
},
);Python
from klarefi import verify_webhook_signature, WebhookVerificationError
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhooks/klarefi", methods=["POST"])
def handle_webhook():
try:
event = verify_webhook_signature(
raw_body=request.get_data(as_text=True),
signature_header=request.headers.get("X-Klarefi-Signature"),
signing_secret="whsec_your_signing_secret",
)
if event["event_type"] == "v1.case.completed":
print("Case completed:", event["data"]["case_id"])
# Process the results in event["data"]
return "", 200
except WebhookVerificationError as e:
print("Webhook verification failed:", e)
return "", 401Event Handling Guidance
Treat webhook events as integration signals, then read the current case or session state from the canonical runtime-backed API surface when you need the latest result.
This avoids coupling your integration to a long list of event-specific assumptions.
Testing Webhooks
Send a test event to verify your receiver works before going live:
curl -X POST https://api.klarefi.com/api/v1/webhooks/test \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"endpoint_url": "https://your-app.com/webhooks/klarefi",
"signing_secret": "whsec_your_signing_secret"
}'Retry Policy
If delivery fails, Klarefi retries with backoff and marks the delivery as
pending, delivering, retrying, delivered, or failed in the dashboard.
Exhausted deliveries are moved to the dead-letter queue and can emit
v1.webhook.delivery_failed to subscribed endpoints. Use the dashboard delivery
logs, delivery_id, and trace_id to debug failures or replay failed
deliveries.