Webhooks
Receive real-time notifications when events occur in Kora Compliance — screenings complete, cases change, risk thresholds are breached, and more.
Create a Subscription
You can configure webhooks via the dashboard (Settings → Webhooks) or programmatically via the API.
Via API
Register a webhook endpoint to receive specific events:
curl -X POST https://api.korastratum.com/api/v1/webhooks/subscriptions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Webhook",
"url": "https://your-server.com/webhooks/compliance",
"events": [
"screening.completed",
"screening.match_found",
"case.created",
"case.escalated",
"risk.threshold_breached",
"batch.completed"
],
"rate_limit_per_minute": 1000
}'
Response:
{
"id": "wh_abc123",
"name": "Production Webhook",
"url": "https://your-server.com/webhooks/compliance",
"events": ["screening.completed", "screening.match_found", "..."],
"status": "ACTIVE",
"secret": "whsec_live_abc123...",
"rate_limit_per_minute": 1000,
"created_at": "2025-06-01T12:00:00Z"
}
Save the secret from the response — it's used to verify webhook signatures and is only shown once.
Event Types
Screening Events
| Event | Description |
|---|---|
screening.started | Screening process initiated |
screening.completed | Screening finished with results |
screening.failed | Screening encountered an error |
screening.match_found | Match discovered in watchlists |
screening.match_resolved | Match reviewed and disposition set |
Case Events
| Event | Description |
|---|---|
case.created | New case created |
case.updated | Case details updated |
case.assigned | Case assigned to analyst |
case.escalated | Case escalated to senior reviewer |
case.closed | Case closed with disposition |
case.reopened | Closed case reopened |
Subject Events
| Event | Description |
|---|---|
subject.created | New subject created |
subject.updated | Subject data updated |
subject.risk_changed | Subject's risk profile changed |
Risk & Decision Events
| Event | Description |
|---|---|
risk.assessed | Risk assessment completed |
risk.level_changed | Risk level changed (e.g., LOW → HIGH) |
risk.threshold_breached | Configured risk threshold exceeded |
decision.made | Automated decision generated |
decision.overridden | Manual override applied |
Identity Events
| Event | Description |
|---|---|
identity.verified | Identity verification passed |
identity.failed | Identity verification failed |
Reporting Events
| Event | Description |
|---|---|
report.generated | Regulatory report created |
report.failed | Report generation failed |
System Events
| Event | Description |
|---|---|
system.alert | System/operational alert |
batch.completed | Batch processing finished |
Webhook Payload
Every webhook delivery has this structure:
{
"id": "del_abc123",
"event": "screening.completed",
"timestamp": "2025-06-01T12:00:00Z",
"data": {
"screening_id": "scr_abc123",
"status": "COMPLETED",
"risk_score": 750,
"risk_band": "HIGH",
"decision": {
"outcome": "REVIEW_REQUIRED",
"triggered_rules": ["SANCTIONS_MATCH"]
},
"match_summary": {
"total_matches": 1,
"sanctions_matches": 1,
"pep_matches": 0
}
}
}
Signature Verification
Every webhook includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Always verify this before processing.
- Node.js
- Python
- Go
const crypto = require("crypto");
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post("/webhooks/compliance", express.raw({ type: "*/*" }), (req, res) => {
const signature = req.headers["x-webhook-signature"];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(payload);
// Process the event...
res.status(200).send("OK");
});
import hmac
import hashlib
def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), payload.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route("/webhooks/compliance", methods=["POST"])
def webhook_handler():
signature = request.headers.get("X-Webhook-Signature")
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return "Invalid signature", 401
event = request.json
# Process the event...
return "OK", 200
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
)
func verifySignature(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expected))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-Webhook-Signature")
if !verifySignature(body, signature, webhookSecret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process the event...
w.WriteHeader(http.StatusOK)
}
Retry Behavior
If your endpoint returns a non-2xx status code or times out (30 seconds), Kora Compliance retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 12 hours |
After 6 failed attempts, the delivery is marked as failed. You can manually retry from the API:
curl -X POST https://api.korastratum.com/api/v1/webhooks/deliveries/del_abc123/retry \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID"
Test a Webhook
Send a test event to verify your endpoint is working:
curl -X POST https://api.korastratum.com/api/v1/webhooks/subscriptions/wh_abc123/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID"
Manage Subscriptions
# List all subscriptions
curl https://api.korastratum.com/api/v1/webhooks/subscriptions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID"
# Update a subscription
curl -X PUT https://api.korastratum.com/api/v1/webhooks/subscriptions/wh_abc123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"events": ["screening.completed", "case.created"],
"status": "ACTIVE"
}'
# Delete a subscription
curl -X DELETE https://api.korastratum.com/api/v1/webhooks/subscriptions/wh_abc123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID"
Best Practices
- Always verify signatures — Reject unverified payloads to prevent spoofing
- Return 200 quickly — Process events asynchronously; return 200 before doing heavy work
- Handle duplicates — Use the delivery
idfor idempotency; the same event may be delivered more than once - Monitor delivery health — Check the deliveries endpoint for failed deliveries
- Use specific events — Subscribe only to the events you need to reduce noise