Server Integration
Build a complete backend integration with Kora Compliance for customer onboarding screening.
Overview
A typical integration flow:
- Customer submits onboarding data to your server
- Your server sends a screening request to Kora Compliance
- Kora Compliance returns a risk score and decision
- Your server acts on the decision (approve, review, or block)
- For async mode, receive results via webhook
Create a Screening
- Node.js (Express)
- Python (Flask)
- Go
const express = require("express");
const app = express();
app.use(express.json());
const API_KEY = process.env.KORA_API_KEY;
const TENANT_ID = process.env.KORA_TENANT_ID;
const BASE_URL = "https://api.korastratum.com/api/v1";
app.post("/onboard", async (req, res) => {
const { name, dateOfBirth, nationality, documentType, documentNumber } =
req.body;
// Screen the customer
const screening = await fetch(`${BASE_URL}/screenings`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"X-Tenant-ID": TENANT_ID,
"Content-Type": "application/json",
},
body: JSON.stringify({
mode: "SYNC",
purpose: "ONBOARDING",
subject: {
type: "INDIVIDUAL",
name,
date_of_birth: dateOfBirth,
nationality,
identifiers: [
{
type: documentType,
value: documentNumber,
country: nationality,
},
],
},
checks: ["SANCTIONS", "PEP", "ADVERSE_MEDIA"],
}),
});
const result = await screening.json();
// Act on the decision
switch (result.decision.outcome) {
case "APPROVE":
// Proceed with onboarding
return res.json({ status: "approved", customerId: "..." });
case "APPROVE_WITH_MONITORING":
// Approve but flag for monitoring
return res.json({ status: "approved", monitoring: true });
case "REVIEW_REQUIRED":
// Queue for compliance review
return res.json({
status: "pending_review",
message: "Your application is under review",
});
case "BLOCK":
// Deny onboarding
return res.status(403).json({
status: "blocked",
message: "Unable to proceed with onboarding",
});
}
});
import os
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
API_KEY = os.environ["KORA_API_KEY"]
TENANT_ID = os.environ["KORA_TENANT_ID"]
BASE_URL = "https://api.korastratum.com/api/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"X-Tenant-ID": TENANT_ID,
"Content-Type": "application/json",
}
@app.route("/onboard", methods=["POST"])
def onboard():
data = request.json
# Screen the customer
response = requests.post(
f"{BASE_URL}/screenings",
headers=HEADERS,
json={
"mode": "SYNC",
"purpose": "ONBOARDING",
"subject": {
"type": "INDIVIDUAL",
"name": data["name"],
"date_of_birth": data["date_of_birth"],
"nationality": data["nationality"],
"identifiers": [
{
"type": data["document_type"],
"value": data["document_number"],
"country": data["nationality"],
}
],
},
"checks": ["SANCTIONS", "PEP", "ADVERSE_MEDIA"],
},
)
result = response.json()
outcome = result["decision"]["outcome"]
if outcome == "APPROVE":
return jsonify({"status": "approved"})
elif outcome == "APPROVE_WITH_MONITORING":
return jsonify({"status": "approved", "monitoring": True})
elif outcome == "REVIEW_REQUIRED":
return jsonify({"status": "pending_review"})
else: # BLOCK
return jsonify({"status": "blocked"}), 403
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
var (
apiKey = os.Getenv("KORA_API_KEY")
tenantID = os.Getenv("KORA_TENANT_ID")
baseURL = "https://api.korastratum.com/api/v1"
)
func onboardHandler(w http.ResponseWriter, r *http.Request) {
var input struct {
Name string `json:"name"`
DateOfBirth string `json:"date_of_birth"`
Nationality string `json:"nationality"`
DocumentType string `json:"document_type"`
DocumentNumber string `json:"document_number"`
}
json.NewDecoder(r.Body).Decode(&input)
body, _ := json.Marshal(map[string]interface{}{
"mode": "SYNC",
"purpose": "ONBOARDING",
"subject": map[string]interface{}{
"type": "INDIVIDUAL",
"name": input.Name,
"date_of_birth": input.DateOfBirth,
"nationality": input.Nationality,
"identifiers": []map[string]string{{
"type": input.DocumentType,
"value": input.DocumentNumber,
"country": input.Nationality,
}},
},
"checks": []string{"SANCTIONS", "PEP", "ADVERSE_MEDIA"},
})
req, _ := http.NewRequest("POST", baseURL+"/screenings", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("X-Tenant-ID", tenantID)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, "screening failed", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
var result struct {
Decision struct {
Outcome string `json:"outcome"`
} `json:"decision"`
}
json.NewDecoder(resp.Body).Decode(&result)
w.Header().Set("Content-Type", "application/json")
switch result.Decision.Outcome {
case "APPROVE", "APPROVE_WITH_MONITORING":
json.NewEncoder(w).Encode(map[string]string{"status": "approved"})
case "REVIEW_REQUIRED":
json.NewEncoder(w).Encode(map[string]string{"status": "pending_review"})
case "BLOCK":
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode(map[string]string{"status": "blocked"})
}
}
Handle Webhook Callbacks
For async screenings or real-time events, set up a webhook handler:
- Node.js
- Python
const crypto = require("crypto");
app.post("/webhooks/compliance", (req, res) => {
// Verify webhook signature
const signature = req.headers["x-webhook-signature"];
const payload = JSON.stringify(req.body);
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(payload)
.digest("hex");
if (signature !== expected) {
return res.status(401).send("Invalid signature");
}
const { event, data } = req.body;
switch (event) {
case "screening.completed":
console.log(`Screening ${data.screening_id}: ${data.decision.outcome}`);
// Process the screening result
break;
case "screening.match_found":
console.log(`Match found: ${data.match.source} (${data.match.score})`);
break;
case "risk.threshold_breached":
console.log(`Risk threshold breached for subject ${data.subject_id}`);
break;
}
res.status(200).send("OK");
});
import hmac
import hashlib
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]
@app.route("/webhooks/compliance", methods=["POST"])
def webhook_handler():
signature = request.headers.get("X-Webhook-Signature")
payload = request.get_data(as_text=True)
expected = hmac.new(
WEBHOOK_SECRET.encode(), payload.encode(), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return "Invalid signature", 401
event = request.json["event"]
data = request.json["data"]
if event == "screening.completed":
print(f"Screening {data['screening_id']}: {data['decision']['outcome']}")
elif event == "screening.match_found":
print(f"Match found: {data['match']['source']}")
elif event == "risk.threshold_breached":
print(f"Risk threshold breached: {data['subject_id']}")
return "OK", 200
Create and Manage Subjects
For ongoing monitoring, create a subject record that persists across screenings:
# Create a subject
curl -X POST https://api.korastratum.com/api/v1/subjects \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"type": "INDIVIDUAL",
"name": "Jane Smith",
"date_of_birth": "1985-03-20",
"nationality": "GB",
"identifiers": [
{"type": "PASSPORT", "value": "GB123456", "country": "GB"}
]
}'
# Screen the subject by ID
curl -X POST https://api.korastratum.com/api/v1/screenings \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{
"mode": "SYNC",
"purpose": "PERIODIC",
"subject_id": "subj_abc123",
"checks": ["SANCTIONS", "PEP"]
}'
# Get the subject's details
curl https://api.korastratum.com/api/v1/subjects/subj_abc123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID"
Error Handling
Always check for error responses and handle them appropriately:
const response = await fetch(`${BASE_URL}/screenings`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"X-Tenant-ID": TENANT_ID,
"Content-Type": "application/json",
},
body: JSON.stringify(screeningPayload),
});
if (!response.ok) {
const error = await response.json();
switch (error.code) {
case "RATE_LIMITED":
// Back off and retry
const retryAfter = response.headers.get("Retry-After");
break;
case "VALIDATION_ERROR":
// Fix the request
console.error("Validation errors:", error.details);
break;
case "SCREENING_TIMEOUT":
// Switch to async mode
break;
default:
console.error(`Error: ${error.code} - ${error.message}`);
}
}
See Error Codes for the full error reference.