Zoi ID کے ساتھ بنائیں
ایک ہوسٹ پر دو API، چھ زبانوں میں ٹائپ والے کلائنٹس، ہوسٹڈ فلو اور سینڈ باکس۔ پوری گائیڈ پڑھنے کے لیے کھلی ہے، اور سینڈ باکس کلیدیں ارلی ایکسس کے ساتھ ملتی ہیں۔
بیس URLhttps://kyc.zoi.network
کوئک اسٹارٹ
- 1
سیشن بنائیں
آپ کا بیک اینڈ اپنی پارٹنر کلید کے ساتھ
POST /v1/partner/sessionsکو کال کرتا ہے۔ - 2
فلو چلائیں
کلائنٹ ٹوکن SDK کو دیں، یا
verificationUrlبراؤزر میں کھولیں۔ - 3
نتیجہ وصول کریں
ویب ہُک کا دستخط جانچیں، پھر شناخت کی ID اور اس کی حالت محفوظ کریں۔
curl https://kyc.zoi.network/v1/partner/sessions \
-H "Authorization: Bearer $ZOI_ID_PARTNER_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-user_42" \
-d '{ "externalUserId": "user_42", "level": "standard", "appId": "com.example.app" }'دو API
پارٹنر API
سرور سے سرور، /v1/partner کے تحت۔ اپنی پارٹنر کلید Bearer ٹوکن کے طور پر بھیجیں۔ اسے کبھی کسی ایپ میں نہ رکھیں۔
کلائنٹ API
SDK اسے /v1/client کے تحت استعمال کرتا ہے۔ یہ ایک کلائنٹ ٹوکن بھیجتا ہے جو ایک سیشن کے لیے 60 منٹ چلتا ہے۔
اصول
- وقت epoch ملی سیکنڈ میں ہوتا ہے، ان فیلڈز میں جن کے نام
Atپر ختم ہوتے ہیں۔ - تاریخیں
YYYY-MM-DDمیں ہوتی ہیں۔ نامعلوم حصےXXہوتے ہیں، جیسے1990-XX-XX۔ - کچھ بنانے یا طے کرنے والی POST درخواستیں
Idempotency-Keyہیڈر قبول کرتی ہیں۔ کلیدیں 24 گھنٹے رکھی جاتی ہیں۔ - سیشن میں تبدیلی کے ساتھ متوقع ریویژن بھیجا جاتا ہے۔ اگر وہ بدل گیا ہو تو آپ کو
revision_conflictملتا ہے۔
سیشن کا بہاؤ
آپ کا بیک اینڈ سیشن بناتا ہے۔ SDK قدم چلاتا ہے۔ سرور ترتیب جانچتا ہے اور کوئی قدم بہت جلد آئے تو step_not_allowed لوٹاتا ہے۔
1. اپنے بیک اینڈ سے سیشن بنائیں
اسی صارف کے لیے دوبارہ کال کرنے پر کھلا سیشن جاری رہتا ہے اور نیا کلائنٹ ٹوکن ملتا ہے۔
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-signup-42
{
"externalUserId": "user_42",
"level": "standard",
"locale": "en",
"appId": "com.example.app"
}
200 OK
{
"sessionId": "ses_...",
"clientToken": "...",
"clientTokenExpiresAt": 1767225600000,
"status": "created",
"apiBaseUrl": "https://kyc.zoi.network",
"resumed": false
}2. SDK جو قدم چلاتا ہے
- 01
سیشن اور باقی ضروری قدم پڑھیں۔
GET /v1/client/session
- 02
رضامندی درج کریں، ساتھ میں چہرے کے موازنے کے لیے الگ اور اختیاری انتخاب۔
POST /v1/client/consent
- 03
ایپ اور ڈیوائس جانچیں۔ نتیجہ فیصلے کا حصہ بنتا ہے۔
POST /v1/client/attestation/challenge, POST /v1/client/attestation
- 04
دستاویز بھیجیں: MRZ اسکین کے بعد چپ پڑھیں، یا تصویریں بھیجیں۔
POST /v1/client/document/mrz-scan, POST /v1/client/document/chip/challenge, POST /v1/client/document/chip or POST /v1/client/document/photos
- 05
جب ایپ سر کی حرکتیں دکھائے، تب سامنے والے کیمرے سے ایک مختصر ویڈیو ریکارڈ کریں۔
POST /v1/client/liveness/challenge, POST /v1/client/liveness
- 06
رہائش کا ملک بھیجیں، اور بہتر سطح پر پتہ بھی۔
POST /v1/client/profile
- 07
صرف پتے کی شرط کے ساتھ: دستاویز اپ لوڈ کریں، پھر پتے کی تصدیق کریں۔ اگر شناختی دستاویز سے پتہ پہلے ہی مل گیا ہو تو یہ قدم چھوڑ دیا جاتا ہے۔
POST /v1/client/address/document, POST /v1/client/address/confirm
- 08
صرف فنڈز کی شرط کے ساتھ: جوابات محفوظ کریں، پالیسی مانگے تو دستاویزات شامل کریں، پھر قدم مکمل کریں۔
POST /v1/client/funds/answers, POST /v1/client/funds/document, POST /v1/client/funds/remove, POST /v1/client/funds/complete
- 09
دو سوالوں کے جواب دیں: کیا آپ سیاسی طور پر نمایاں شخص ہیں، اور کیا آپ اپنی طرف سے کام کر رہے ہیں۔
POST /v1/client/declarations
- 10
موجودہ ریویژن کے ساتھ جمع کریں۔
POST /v1/client/submit
صارف کسی خودکار نتیجے پر انسانی جائزے کی درخواست بھی کر سکتا ہے (POST /review-request) یا رضامندی واپس لے سکتا ہے، جس سے سیشن منسوخ ہوتا ہے اور اس کی فائلیں فوراً حذف ہوتی ہیں (POST /withdraw)۔
پتے کا ثبوت اور فنڈز کا ذریعہ
شناخت سے آگے تصدیق کے لیے POST /v1/partner/sessions میں requirements شامل کریں۔ address پتے کا ثبوت مانگتا ہے۔ source_of_funds پوچھتا ہے کہ پیسہ کہاں سے آتا ہے، اور source_of_wealth کل دولت اور اس کے بننے کا طریقہ بھی پوچھتا ہے۔ فنڈز کی یہ دونوں قدریں مل کر ایک قدم ہیں، اور دونوں address کے ساتھ مل سکتی ہیں۔
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
{
"externalUserId": "user_42",
"level": "standard",
"appId": "com.example.app",
"requirements": ["address", "source_of_funds"],
// Optional. Without them your partner default applies, else ours.
"addressPolicy": { "maxAgeDays": 90, "signedOnly": false },
"fundsPolicy": { "evidence": "optional", "currency": "EUR" }
}اختیاری پالیسیاں
پالیسی نہ ہو تو آپ کی پارٹنر ڈیفالٹ لاگو ہوتی ہے، ورنہ ہماری۔ سیشن بنتے وقت پالیسی اس میں کاپی ہو جاتی ہے۔
addressPolicy:maxAgeDays(ڈیفالٹ 90، 1 سے 366)،acceptedTypes(ڈیفالٹ: یوٹیلیٹی، ٹیلی کام اور بینک دستاویزات، ٹیکس دستاویزات، سرکاری خطوط اور رہائش کے سرٹیفکیٹ) اورsignedOnly(ڈیفالٹ false)۔fundsPolicy:questions،evidence(none،optionalیاrequired، ڈیفالٹnone)،acceptedTypes،maxAgeDays(ڈیفالٹ 180)،currency(ISO 4217، ڈیفالٹ USD) اور رینجز کے لیےbrackets۔- دوسری شرائط والا کوئی کھلا سیشن جو ابھی جمع نہیں ہوا، منسوخ ہو جاتا ہے اور نیا سیشن شروع ہوتا ہے، بالکل سطح بدلنے کی طرح۔
- ہر نتیجہ سینڈ باکس منظرناموں سے ٹیسٹ کریں، جیسے
address_signed_pdf،address_document_review،funds_signed_documentsاورfunds_review۔
فالو اپ سیشنز
ایسے صارف کے لیے صرف شرائط کی تصدیق کریں جس کی شناخت آپ کے پاس پہلے سے فعال ہے۔ requirements کے ساتھ reuseIdentity: true بھیجیں۔
- صارف رضامندی دیتا ہے اور صرف نئے قدم مکمل کرتا ہے۔ دستاویز، سیلفی یا بیانات دوبارہ نہیں مانگے جاتے۔
- منظوری پر شناخت اپنی ID، تصدیق کی تاریخ اور میعاد برقرار رکھتی ہے، اور اسے نیا نتیجہ ملتا ہے۔ آپ کو
session.approvedاورidentity.updatedملتے ہیں۔ - فعال شناخت کے بغیر کال کا جواب 409
step_not_allowedہوتا ہے۔ - اسے اپنے رسک اصولوں سے شروع کریں، جیسے بڑی رقم جمع ہونا، PEP فلیگ یا پرخطر ملک۔ آپ کے اصول آپ کے بیک اینڈ میں، ایک ہی جگہ رہتے ہیں۔
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: edd-user_42-2026-11
{
"externalUserId": "user_42",
"appId": "com.example.app",
"requirements": ["source_of_funds", "source_of_wealth"],
"reuseIdentity": true,
"fundsPolicy": { "evidence": "required" }
}
// No active identity for user_42:
409 { "error": { "code": "step_not_allowed", ... } }نتائج اور اسکوپس
منظور شدہ سیشنز میں address اور funds ہوتے ہیں، اور شناخت ہر ایک کا تازہ ترین نتیجہ رکھتی ہے۔ خلاصے کی فیلڈز ہر اسکوپ کے ساتھ آتی ہیں۔ تفصیلات کے لیے اسکوپ چاہیے۔
- اسکوپ
address: پتے کی سطریں۔ اس کے بغیر آپ کوverified،method،evidenceLevel،documentType،documentDate،verifiedAtاورcountryملتے ہیں۔ - اسکوپ
financial_data:answers،documentsاورfindings۔ اس کے بغیر آپ کوkinds،evidenceLevel،reviewed،documentCountاورverifiedAtملتے ہیں۔ یہ کبھیeligibilityکا حصہ نہیں ہوتا۔ evidenceLevelکی قدرhigh،medium،lowیاnoneہوتی ہے۔ پتے کے طریقے، سب سے مضبوط پہلے:wallet،id_document،signed_pdf،official_code،document_reviewاورdeclared۔- دستاویزات API کے ذریعے نہیں بھیجی جاتیں۔ جب انوائرمنٹ کے پاس
financial_dataاسکوپ ہو تو کمپلائنس رول والے ممبرز ڈیش بورڈ میں فنڈز کے ذریعے کی دستاویزات کے صفحات دیکھ سکتے ہیں۔ ہر بار دیکھنا ریکارڈ ہوتا ہے۔
"session": {
"id": "ses_...",
"status": "approved",
"requirements": ["address", "source_of_funds"],
"reusesIdentity": false,
"address": {
"verified": true,
"method": "signed_pdf",
"evidenceLevel": "high",
"documentType": "bank_statement",
"documentDate": "2026-10-14",
"verifiedAt": 1767225600000,
"country": "DE",
// scope "address"
"address": { "line1": "Musterstrasse 12", "city": "Berlin", "postalCode": "10115", "country": "DE" }
},
"funds": {
"kinds": ["source_of_funds"],
"evidenceLevel": "medium",
"reviewed": true,
"documentCount": 2,
"verifiedAt": 1767225600000,
// scope "financial_data"
"answers": {
"employmentStatus": "employed",
"income": { "min": 50000, "max": 100000, "currency": "EUR" },
"sources": ["salary", "savings"],
...
},
"documents": [
{ "documentType": "payslip", "documentDate": "2026-10-31", "amount": 5200, "currency": "EUR",
"signature": "valid", "signedByKnownIssuer": true, "nameMatch": "match", "findings": [], ... },
...
],
"findings": []
}
}ویب فلو
SDK نہیں؟ صارف کو ہمارے ہوسٹ کیے گئے صفحے پر بھیجیں۔ یہ وہی API، وہی جانچ اور وہی ریویو قطار استعمال کرتا ہے۔
1. لنک حاصل کریں
POST /v1/partner/sessions سے verificationUrl ملتا ہے۔ آخر میں صارف کو واپس بھیجنے کے لیے redirectUrl دیں۔ یہ آپ کے رجسٹرڈ ری ڈائریکٹ origins میں سے کسی ایک سے شروع ہونا چاہیے۔
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
{
"externalUserId": "user_42",
"level": "standard",
"locale": "en",
"appId": "com.example.app",
"redirectUrl": "https://example.com/kyc/done"
}
200 OK
{
"sessionId": "ses_...",
"clientToken": "...",
"verificationUrl": "https://kyc.zoi.network/verify/#token=...&lang=en",
...
}- کلائنٹ ٹوکن URL کے fragment میں ہوتا ہے، یعنی
#کے بعد۔ براؤزر fragment کبھی سرور کو نہیں بھیجتے، اس لیے ٹوکن لاگز یاRefererہیڈر میں نہیں آتا۔ صفحہ اسے پڑھتا ہے، ایڈریس بار سے ہٹا دیتا ہے اور صرف میموری میں رکھتا ہے۔ - 19 زبانوں میں سے ایک چننے کے لیے fragment میں
langشامل کریں۔ اس کے بغیر صفحہ براؤزر کی زبان استعمال کرتا ہے۔ - صفحہ دوبارہ لوڈ کرنے سے ٹوکن ختم ہو جاتا ہے۔ اپنے بیک اینڈ سے اسی سیشن کے لیے نیا لنک بنائیں۔
- صفحہ رضامندی کے ساتھ
channel: "web"بھیجتا ہے۔ براؤزر شناختی کارڈ کی چپ نہیں پڑھ سکتے، اس لیے ویب سیشن دستاویز کی تصاویر یا ڈیجیٹل ID والٹ استعمال کرتے ہیں۔ یہ ڈیوائس کی جانچ چھوڑ دیتے ہیں، اور ویب سے آنے والی دستاویز کی تصاویر کا فیصلہ ہمیشہ تربیت یافتہ جائزہ کار کرتا ہے۔ - کمپیوٹر پر صفحہ ایک QR کوڈ دکھاتا ہے تاکہ صارف فون پر جاری رکھ سکے۔
2. جب صارف مکمل کر لے
جب فلو ختم ہوتا ہے (بھیج دیا گیا، منسوخ ہوا یا آخری حالت)، صفحہ یہ پیغام ان میں سے ہر موجود چیز کو بھیجتا ہے: window.ReactNativeWebView، window.webkit.messageHandlers.zoiId، window.ZoiIdAndroid اور window.opener۔ پھر یہ zoiIdSession اور zoiIdStatus شامل کر کے redirectUrl کھولتا ہے۔
پیغام یا ری ڈائریکٹ پیرامیٹرز پر کبھی بھروسہ نہ کریں۔ صرف اپنے webhook یا GET /v1/partner/sessions/:id پر بھروسہ کریں۔
// Sent to each bridge that exists:
// window.ReactNativeWebView.postMessage(JSON string)
// window.webkit.messageHandlers.zoiId.postMessage(object)
// window.ZoiIdAndroid.postMessage(JSON string)
// window.opener.postMessage(object, "<origin of your redirectUrl>")
// (popups only, and only when the session has a redirectUrl)
{
"source": "zoi-id",
"type": "finished", // or "cancelled"
"sessionId": "ses_...",
"status": "processing" // the session status at that moment
}
// Then, if redirectUrl was set (the page uses the one stored with the
// session, never one from the address bar):
https://example.com/kyc/done?zoiIdSession=ses_...&zoiIdStatus=processing3. ویب ویو کی سیٹنگز
Android WebView
javaScriptEnabled اور domStorageEnabled آن کریں اور mediaPlaybackRequiresUserGesture کو false کریں۔ جب آپ کی ایپ کو CAMERA اجازت مل جائے تو WebChromeClient میں onPermissionRequest پر RESOURCE_VIDEO_CAPTURE کی اجازت دیں۔ تصویر والے متبادل کے لیے فائل چننے کا آپشن شامل کریں۔
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
// Only after your app holds Manifest.permission.CAMERA.
if (request.resources.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE))
} else {
request.deny()
}
}
override fun onShowFileChooser(
view: WebView, callback: ValueCallback<Array<Uri>>, params: FileChooserParams,
): Boolean {
// Open a picker and pass the result to callback (photo fallback).
return true
}
}iOS WKWebView
iOS 14.3 یا نیا۔ allowsInlineMediaPlayback کو true کریں اور اپنی ایپ میں NSCameraUsageDescription شامل کریں۔ iOS 15 اور نئے ورژن میں آپ کیمرے کی اجازت کا فیصلہ UI delegate میں کر سکتے ہیں۔
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self // iOS 15+: decide camera access below
func webView(_ webView: WKWebView,
requestMediaCapturePermissionFor origin: WKSecurityOrigin,
initiatedByFrame frame: WKFrameInfo,
type: WKMediaCaptureType,
decisionHandler: @escaping (WKPermissionDecision) -> Void) {
decisionHandler(origin.host == "kyc.zoi.network" ? .grant : .deny)
}
// Info.plist: NSCameraUsageDescriptionReact Native WebView
mediaCapturePermissionGrantType="grant"، allowsInlineMediaPlayback اور javaScriptEnabled سیٹ کریں۔ آپ کی ایپ کو کیمرے کی اجازت چاہیے۔
<WebView
source={{ uri: verificationUrl }}
javaScriptEnabled
allowsInlineMediaPlayback
mediaCapturePermissionGrantType="grant"
onMessage={(event) => {
const message = JSON.parse(event.nativeEvent.data);
if (message.source === "zoi-id") {
// Close the webview, then ask your backend for the real status.
}
}}
/>کیمرے کی اجازتوں اور clickjacking کی وجہ سے iframe سپورٹ نہیں ہوتے۔ براؤزر ٹیب، پاپ اپ یا ویب ویو استعمال کریں۔
حالتیں
سیشن
created- سیشن بن گیا ہے، ابھی کچھ اپلوڈ نہیں ہوا۔
in_progress- رضامندی دی گئی، قدم اپلوڈ ہو رہے ہیں۔
processing- جمع ہو گیا، جانچ جاری ہے۔
review- تربیت یافتہ جائزہ کار کا انتظار۔
approved- حتمی۔ شناخت کی تصدیق ہو گئی۔
rejected- حتمی۔ وجہ کے کوڈ دیکھیں۔
resubmit- صارف کو requiredSteps کے قدم دوبارہ کرنے ہوں گے۔
expired- وقت پر حتمی فیصلہ نہیں ہوا۔
cancelled- پارٹنر، صارف یا ڈیٹا حذف کرنے سے منسوخ۔
شناخت
active- قابل استعمال۔
suspended- رکی ہوئی، مثلاً پابندیوں سے کسی نئے ممکنہ میل کے بعد۔
expired- دستاویز یا تصدیق کی مدت ختم۔ نیا سیشن دوبارہ تصدیق کرتا ہے۔
revoked- حتمی۔
SDK
React Native (Expo) SDK آپ کو ٹائپ والا API کلائنٹ، NFC چپ ریڈنگ اور ڈیوائس کی جانچ دیتا ہے۔ یہ ہیڈلیس ہے، اس لیے آپ کی ایپ اپنی اسکرینوں اور کیمرے پر پورا کنٹرول رکھتی ہے۔ اپنے سرورز کے لیے TypeScript، Python، Go، PHP، Java اور Ruby کے ٹائپ والے کلائنٹس استعمال کریں۔
import { ZoiIdClient, nextStep } from "@zoi/id-sdk";
// clientToken and apiBaseUrl come from your backend (POST /v1/partner/sessions).
const client = new ZoiIdClient({ baseUrl: apiBaseUrl, clientToken });
const session = await client.getSession();
switch (nextStep(session)) {
case "consent": /* show the notice and the biometric choice */ break;
case "document": /* chip read or photos */ break;
case "liveness": /* record the video */ break;
case "address": /* upload a document, then confirm the address */ break;
case "funds": /* answers, then documents when the policy asks */ break;
case "submit": await client.submit(session.revision); break;
case "done": /* wait for your backend to get the webhook */ break;
}ویب ہُک
جب کوئی سیشن یا شناخت بدلتی ہے، ہم ویب ہُک بھیجتے ہیں۔ مواد پر بھروسہ کرنے سے پہلے ہمیشہ دستخط جانچیں۔
- ہر ویب ہُک میں
Zoi-Id-Signatureہیڈر ہوتا ہے:t=unix seconds,v1=hex۔ v1آپ کے ویب ہُک سیکرٹ سے متنt.raw bodyکا HMAC-SHA256 ہے۔ JSON پارس کرنے سے پہلے والی خام باڈی استعمال کریں۔- جب سیکرٹ بدلا جا رہا ہو، ہیڈر میں ہر فعال سیکرٹ کے لیے ایک
v1ہوتا ہے۔ اگر کوئی بھی میل کھائے تو ویب ہُک قبول کریں۔ - اگر
tموجودہ وقت سے 300 سیکنڈ سے زیادہ دور ہو تو اسے رد کریں۔ - ناکام ترسیل 72 گھنٹے تک دوبارہ کی جاتی ہے۔ ہر بار نئے
tکے ساتھ دوبارہ دستخط ہوتا ہے۔ - شناختوں میں
revisionہوتا ہے۔ کوئی ایونٹ تبھی لاگو کریں جب اس کا ریویژن آپ کے محفوظ کردہ سے نیا ہو۔
POST <your webhook URL>
Zoi-Id-Signature: t=1767225600,v1=5f2b...e9
{
"v": 1,
"eventId": "evt_...",
"type": "identity.suspended",
"createdAt": 1767225600000,
"object": "identity",
"externalUserId": "user_42",
"identity": {
"identityId": "idn_...",
"revision": 7,
"status": "suspended",
"level": "standard",
"verifiedAt": 1764547200000,
"validUntil": 1859155200000,
"ageOver18": true,
"captureMethod": "nfc"
}
}ایونٹ کی اقسام
session.processingsession.reviewsession.approvedsession.rejectedsession.resubmitsession.expiredsession.cancelledidentity.updatedidentity.suspendedidentity.reinstatedidentity.expiredidentity.revoked
TypeScript میں دستخط جانچیں
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 300;
export function verifyZoiIdSignature(
rawBody: string,
header: string | null,
secret: string,
nowSeconds = Math.floor(Date.now() / 1000),
): boolean {
if (!header) return false;
let t: string | undefined;
const signatures: Buffer[] = [];
for (const part of header.split(",")) {
const [key, value] = part.trim().split("=");
if (!value) continue;
if (key === "t") t = value;
if (key === "v1" && /^[0-9a-f]+$/i.test(value)) signatures.push(Buffer.from(value, "hex"));
}
if (!t || !/^\d+$/.test(t) || signatures.length === 0) return false;
if (Math.abs(nowSeconds - Number(t)) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
return signatures.some((sig) => sig.length === expected.length && timingSafeEqual(sig, expected));
}
// Example handler: read the raw body first, parse JSON only after the check.
export async function POST(request: Request) {
const rawBody = await request.text();
const ok = verifyZoiIdSignature(rawBody, request.headers.get("Zoi-Id-Signature"), process.env.ZOI_ID_WEBHOOK_SECRET!);
if (!ok) return new Response("bad signature", { status: 400 });
const event = JSON.parse(rawBody);
// Store event.eventId to skip duplicates, then apply it if the revision is newer.
return new Response(null, { status: 204 });
}Go میں دستخط جانچیں
package zoiid
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
const toleranceSeconds = 300
// VerifySignature checks the Zoi-Id-Signature header against the raw body.
func VerifySignature(rawBody []byte, header, secret string, now time.Time) bool {
var t string
var sigs [][]byte
for _, part := range strings.Split(header, ",") {
key, value, ok := strings.Cut(strings.TrimSpace(part), "=")
if !ok || value == "" {
continue
}
switch key {
case "t":
t = value
case "v1":
if b, err := hex.DecodeString(value); err == nil {
sigs = append(sigs, b)
}
}
}
ts, err := strconv.ParseInt(t, 10, 64)
if err != nil || len(sigs) == 0 {
return false
}
if d := now.Unix() - ts; d > toleranceSeconds || d < -toleranceSeconds {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(t + "."))
mac.Write(rawBody)
expected := mac.Sum(nil)
for _, sig := range sigs {
if hmac.Equal(sig, expected) {
return true
}
}
return false
}ایونٹ فیڈ
GET /v1/partner/events پچھلے 30 دنوں کے تمام ایونٹ لوٹاتا ہے، سب سے پرانا پہلے۔ اگلا صفحہ پڑھنے کے لیے آخری nextCursor کو after میں بھیجیں۔ لمبی رکاوٹ کے بعد، یا یہ یقینی بنانے کے لیے کہ کچھ نہیں چھوٹا، اسے استعمال کریں۔
GET /v1/partner/events?after=<nextCursor>&limit=100
Authorization: Bearer <partner key>
200 OK
{
"items": [ /* webhook payloads, oldest first */ ],
"nextCursor": "..." // null when there is nothing more
}فراڈ رپورٹس
جب کوئی فیصلہ شدہ سیشن فراڈ نکلے تو ہمیں بتائیں، مثلاً کسی چارج بیک کے بعد۔ رپورٹس سے ہم غلط منظوریوں کی پیمائش کرتے ہیں۔ یہ سیشن، شناخت یا صارف کا نتیجہ نہیں بدلتیں۔
- اسکوپ
sessions:write۔ صرف فیصلہ شدہ سیشنز کے لیے (منظور یا مسترد)۔ فیصلے سے پہلے کال کا جواب 409 ہوتا ہے۔ - خالی باڈی
confirmed_fraudکوotherقسم کے ساتھ رپورٹ کرتی ہے۔statusکی قدرsuspected_fraudبھی ہو سکتی ہے، یا اپنی رپورٹ واپس لینے کے لیےcleared۔ - اقسام:
forged_document،impostor،deepfake_or_replay،synthetic_identity،account_takeoverاورother۔clearedکے ساتھ کوئی قسم نہیں۔ noteہمارے تجزیہ کاروں کے لیے آزاد متن ہے، زیادہ سے زیادہ 1000 حروف۔ اس میں کبھی نام، دستاویز نمبر یا کوئی اور ذاتی ڈیٹا نہ لکھیں۔- نئی رپورٹ کا جواب 201 ہوتا ہے۔ وہی رپورٹ دوبارہ بھیجنے پر محفوظ شدہ رپورٹ 200 کے ساتھ لوٹتی ہے۔ محفوظ طریقے سے دوبارہ کوشش کے لیے
Idempotency-Keyبھیجیں۔ ہر سیشن پر زیادہ سے زیادہ 20 رپورٹس۔ - یہی رپورٹ ڈیش بورڈ میں سیشن کے صفحے پر بس ایک کلک دور ہے۔
POST /v1/partner/sessions/ses_.../fraud-reports
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: chargeback-8812
{
"status": "confirmed_fraud",
"category": "impostor",
"note": "Chargeback on the first deposit"
}
201 Created
{
"id": "flb_...",
"sessionId": "ses_...",
"status": "confirmed_fraud",
"category": "impostor",
"noteKept": true,
"createdAt": 1767225600000
}ڈیٹا حذف کرنا
DELETE /v1/partner/users/:externalUserId صارف کا ڈیٹا اس وقت حذف کرتا ہے جب وہ اکاؤنٹ بند کرے یا حذف کرنے کو کہے۔ جواب بتاتا ہے کہ ابھی کیا حذف ہوا، اور کیا رکھا گیا، کیوں اور کب تک۔
DELETE /v1/partner/users/user_42
Authorization: Bearer <partner key>
200 OK
{
"deleted": ["images", "chip_files", "video", "personal_data", "address"],
"retained": [
{ "category": "dedup_keys", "reason": "fraud_prevention", "until": 1924992000000 },
{ "category": "sanctions_screening_records", "reason": "legal_obligation", "until": 2082758400000 }
],
"backupsPurgedWithinDays": 30
}ایررز
ہر ایرر کی شکل ایک جیسی ہے اور ہر کوڈ کا HTTP اسٹیٹس طے ہے۔
{
"error": {
"code": "step_not_allowed",
"message": "This step is not allowed now.",
"requestId": "req_..."
}
}| کوڈ | HTTP | مطلب |
|---|---|---|
bad_request | 400 | درخواست معاہدے سے میل نہیں کھاتی۔ |
unauthorized | 401 | کلید یا ٹوکن موجود نہیں یا غلط ہے۔ |
token_expired | 401 | کلائنٹ ٹوکن کی میعاد ختم ہو گئی۔ اپنے بیک اینڈ سے نیا بنائیں۔ |
forbidden | 403 | یہ کلید یہ کام نہیں کر سکتی۔ |
not_found | 404 | ایسا کوئی سیشن یا صارف نہیں۔ |
session_closed | 409 | سیشن حتمی ہے۔ مزید اپلوڈ نہیں۔ |
step_not_allowed | 409 | غلط ترتیب، یا ابھی اس قدم کی ضرورت نہیں۔ |
revision_conflict | 409 | سیشن کا ریویژن بدل گیا۔ دوبارہ پڑھیں۔ |
idempotency_conflict | 409 | ایک ہی Idempotency-Key کسی دوسری درخواست کے ساتھ استعمال ہوئی۔ |
challenge_expired | 410 | چیلنج کی میعاد ختم ہو گئی یا پہلے استعمال ہو چکا۔ نیا مانگیں۔ |
payload_too_large | 413 | اپلوڈ بہت بڑا ہے۔ |
unsupported_media_type | 415 | یہ فائل کی قسم قبول نہیں۔ |
rate_limited | 429 | بہت زیادہ درخواستیں۔ بعد میں دوبارہ کوشش کریں۔ |
internal | 500 | ہماری طرف کچھ غلط ہو گیا۔ |
dependency_unavailable | 503 | جس سروس کی ہمیں ضرورت ہے وہ بند ہے۔ بعد میں دوبارہ کوشش کریں۔ |
وجہ کے کوڈ
جب دوبارہ بھیجنا ہو یا رد ہو جائے تو سیشن میں reasonCodes ہوتے ہیں۔ ہر کوڈ کے لیے اپنا ترجمہ شدہ پیغام دکھائیں۔ صارف کو کبھی خام کوڈ نہ دکھائیں۔
consent_missingconsent_withdrawnattestation_faileddocument_expireddocument_unreadabledocument_not_supporteddocument_suspected_tamperingdocument_number_visiblechip_verification_failedchip_proof_missingchip_data_mismatchmrz_invalidphoto_qualityface_not_foundface_mismatchliveness_failedunderagelocation_not_supportedresidence_not_supportedsanctions_matchpep_reviewduplicate_identitypossible_duplicatedeclaration_requires_reviewuser_requested_reviewreviewer_otheraddress_unreadableaddress_document_too_oldaddress_document_not_acceptedaddress_name_mismatchaddress_signature_requiredfunds_document_unreadablefunds_document_too_oldfunds_document_not_acceptedfunds_more_evidencefunds_evidence_rejected