Інтеграція KYC-провайдера (Sumsub, Onfido, Jumio)
Вибір KYC-провайдера впливає на conversion rate, вартість верифікації та регіональне покриття. Інтеграція займає 1-3 тижні залежно від провайдера та вимог — головна складність не технічні деталі API, а коректна обробка edge cases та webhook логіка.
Порівняння провайдерів
| Параметр | Sumsub | Onfido | Jumio |
|---|---|---|---|
| Покриття документів | 220+ країн | 195+ країн | 200+ країн |
| Crypto compliance | Нативна підтримка | Обмежена | Обмежена |
| Вартість | Середня | Вище середнього | Вище середнього |
| Найкращий для | Crypto/fintech WW | ЄС ринок | Enterprise KYB |
| Якість SDK | Відмінна | Хороша | Хороша |
Для крипто-проектів: Sumsub — стандартний вибір через нативну crypto compliance логіку.
Інтеграція Sumsub
Backend генерація токена
import crypto from "crypto";
import axios from "axios";
const SUMSUB_APP_TOKEN = process.env.SUMSUB_APP_TOKEN!;
const SUMSUB_SECRET_KEY = process.env.SUMSUB_SECRET_KEY!;
function createSignature(timestamp: number, method: string, url: string, body?: string): string {
const data = timestamp + method + url + (body || "");
return crypto.createHmac("sha256", SUMSUB_SECRET_KEY).update(data).digest("hex");
}
async function createAccessToken(userId: string, levelName: string): Promise<string> {
const timestamp = Math.floor(Date.now() / 1000);
const url = `/resources/accessTokens?userId=${userId}&levelName=${levelName}&ttlInSecs=1800`;
const response = await axios.post(`https://api.sumsub.com${url}`, {}, {
headers: {
"X-App-Token": SUMSUB_APP_TOKEN,
"X-App-Access-Sig": createSignature(timestamp, "POST", url),
"X-App-Access-Ts": timestamp,
},
});
return response.data.token;
}
Обробка webhook
app.post("/webhooks/sumsub", express.raw({ type: "application/json" }), async (req, res) => {
const signature = req.headers["x-payload-digest"] as string;
const secret = process.env.SUMSUB_WEBHOOK_SECRET!;
const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send("Invalid signature");
}
const payload = JSON.parse(req.body.toString());
switch (payload.type) {
case "applicantReviewed":
await handleApplicantReviewed(payload);
break;
case "applicantPending":
await handleApplicantPending(payload.applicantId);
break;
case "applicantPersonalInfoChanged":
await handlePersonalInfoChanged(payload.applicantId);
break;
}
res.status(200).send("OK");
});
async function handleApplicantReviewed(payload: any) {
const { applicantId, reviewResult } = payload;
const userId = await getUserByApplicantId(applicantId);
if (reviewResult.reviewAnswer === "GREEN") {
await approveUser(userId, applicantId);
} else if (reviewResult.reviewAnswer === "RED") {
const reasons = reviewResult.reviewRejectType; // масив причин
await rejectUser(userId, reasons);
} else if (reviewResult.reviewAnswer === "YELLOW") {
// Потребує ручної перевірки compliance офіцером
await flagForManualReview(userId, applicantId);
}
}
Frontend SDK (React)
import SumsubWebSdk from "@sumsub/websdk";
import { useEffect, useRef } from "react";
interface KYCWidgetProps {
userId: string;
levelName: string;
onApproved: () => void;
onRejected: (reason: string) => void;
}
export function KYCWidget({ userId, levelName, onApproved, onRejected }: KYCWidgetProps) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let sdk: any;
async function initSDK() {
const { accessToken } = await fetch("/api/kyc/token", {
method: "POST",
body: JSON.stringify({ userId, levelName }),
headers: { "Content-Type": "application/json" },
}).then(r => r.json());
sdk = SumsubWebSdk.init(accessToken, () => refreshKYCToken(userId), {
lang: "uk",
onMessage: (type: string, payload: any) => {
if (type === "idCheck.onApplicantStatusChanged") {
if (payload.reviewResult?.reviewAnswer === "GREEN") onApproved();
if (payload.reviewResult?.reviewAnswer === "RED") {
onRejected(payload.reviewResult.reviewRejectType?.[0] || "unknown");
}
}
},
});
sdk.launch(containerRef.current);
}
initSDK();
return () => sdk?.destroy();
}, [userId]);
return <div ref={containerRef} style={{ minHeight: "600px" }} />;
}
Інтеграція Onfido (для ринку ЄС)
import { DefaultApi, Configuration } from "@onfido/api";
const onfido = new DefaultApi(new Configuration({ apiToken: ONFIDO_API_TOKEN }));
// Створення applicant
const applicant = await onfido.createApplicant({
firstName: "Ivan",
lastName: "Petrov",
email: "[email protected]",
});
// SDK токен для frontend
const sdkToken = await onfido.generateSdkToken({
applicantId: applicant.id,
referrer: "https://yoursite.com/*",
});
// Запуск перевірки після завантаження документа
const check = await onfido.createCheck({
applicantId: applicant.id,
reportNames: ["document", "facial_similarity_photo", "watchlist_enhanced"],
});
Onfido використовує watchlist_enhanced для PEP/sanctions скрининга в тому ж запиті — зручно для EU compliance.
Повна інтеграція KYC провайдера (backend API + webhook + frontend SDK + admin UI) — 2-3 тижні.







