Server Integration
This guide covers the server-side integration: creating verifications, passing IDs to your mobile app, and handling webhook results.
Architecture
Your server handles two things:
- Create verifications — call the Kora IDV API to create a session, then pass the verification ID to your mobile app
- Receive webhooks — process verification results asynchronously when they complete
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ Mobile App │◀────────│ Your Server │◀────────│ Kora IDV │
│ │ ver ID │ │ webhook │ API │
└──────┬───────┘ └──────┬───────┘ └──────────────┘
│ │
│ SDK captures │ POST /verifications
│ doc + selfie │
└─────────────────────────┘
Express.js (Node.js)
const express = require("express");
const crypto = require("crypto");
const app = express();
app.use(express.json({ limit: "10mb" }));
const API_BASE = "https://api.korastratum.com/api/v1/idv";
const API_KEY = process.env.KORAIDV_API_KEY;
const TENANT_ID = process.env.KORAIDV_TENANT_ID;
const WEBHOOK_SECRET = process.env.KORAIDV_WEBHOOK_SECRET;
// Create a verification — called by your mobile app before launching the SDK
app.post("/api/verifications", async (req, res) => {
const { userId, tier } = req.body;
const response = await fetch(`${API_BASE}/verifications`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"X-Tenant-ID": TENANT_ID,
"Content-Type": "application/json",
},
body: JSON.stringify({
externalId: userId,
tier: tier || "standard",
callbackUrl: "https://your-server.com/webhooks/koraidv",
}),
});
const verification = await response.json();
res.json({ verificationId: verification.id });
});
// Webhook handler — receives verification results
app.post("/webhooks/koraidv", (req, res) => {
// Verify signature
const signature = req.headers["x-kora-signature"];
const expectedSignature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
return res.status(401).json({ error: "Invalid signature" });
}
const { eventType, data } = req.body;
switch (eventType) {
case "verification.completed":
handleVerificationCompleted(data);
break;
case "verification.verified":
handleVerificationApproved(data);
break;
case "verification.rejected":
handleVerificationRejected(data);
break;
case "fraud_alert.created":
handleFraudAlert(data);
break;
}
res.status(200).json({ received: true });
});
function handleVerificationCompleted(data) {
console.log(`Verification ${data.verificationId}: ${data.decision}`);
// Update your user record based on data.decision
}
function handleVerificationApproved(data) {
// Activate the user's account
}
function handleVerificationRejected(data) {
// Notify the user, offer retry
}
function handleFraudAlert(data) {
// Alert your compliance team
}
app.listen(3000);
Flask (Python)
import hmac
import hashlib
import json
import os
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
API_BASE = "https://api.korastratum.com/api/v1/idv"
API_KEY = os.environ["KORAIDV_API_KEY"]
TENANT_ID = os.environ["KORAIDV_TENANT_ID"]
WEBHOOK_SECRET = os.environ["KORAIDV_WEBHOOK_SECRET"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"X-Tenant-ID": TENANT_ID,
"Content-Type": "application/json",
}
@app.post("/api/verifications")
def create_verification():
data = request.json
response = requests.post(
f"{API_BASE}/verifications",
headers=HEADERS,
json={
"externalId": data["userId"],
"tier": data.get("tier", "standard"),
"callbackUrl": "https://your-server.com/webhooks/koraidv",
},
)
verification = response.json()
return jsonify({"verificationId": verification["id"]})
@app.post("/webhooks/koraidv")
def webhook_handler():
# Verify signature
signature = request.headers.get("X-Kora-Signature", "")
expected = hmac.new(
WEBHOOK_SECRET.encode(),
request.data,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
return jsonify({"error": "Invalid signature"}), 401
payload = request.json
event_type = payload["eventType"]
data = payload["data"]
if event_type == "verification.completed":
# Update user record based on data["decision"]
print(f"Verification {data['verificationId']}: {data['decision']}")
elif event_type == "fraud_alert.created":
# Alert compliance team
print(f"Fraud alert: {data['alertType']} - {data['severity']}")
return jsonify({"received": True})
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
)
var (
apiBase = "https://api.korastratum.com/api/v1/idv"
apiKey = os.Getenv("KORAIDV_API_KEY")
tenantID = os.Getenv("KORAIDV_TENANT_ID")
webhookSecret = os.Getenv("KORAIDV_WEBHOOK_SECRET")
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
// Verify signature
signature := r.Header.Get("X-Kora-Signature")
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(signature), []byte(expected)) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
var payload struct {
EventType string `json:"eventType"`
Data json.RawMessage `json:"data"`
}
json.Unmarshal(body, &payload)
switch payload.EventType {
case "verification.completed":
log.Printf("Verification completed: %s", payload.Data)
case "fraud_alert.created":
log.Printf("Fraud alert: %s", payload.Data)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func main() {
http.HandleFunc("/webhooks/koraidv", webhookHandler)
log.Fatal(http.ListenAndServe(":3000", nil))
}
Checklist
Before going live, verify:
- Verification creation works with your production API key
- Mobile SDK launches with the verification ID from your server
- Webhook endpoint receives and processes events
- Webhook signature verification is enabled
- You handle all three decisions:
auto_approve,auto_reject,manual_review - Error responses (400, 401, 429, 500) are handled gracefully
- Rate limits are respected with exponential backoff