Skip to main content

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:

  1. Create verifications — call the Kora IDV API to create a session, then pass the verification ID to your mobile app
  2. 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