सीधे सामग्री पर जाएं
Zoi ID

Zoi ID के साथ बनाएं

एक होस्ट पर दो API, छह भाषाओं में टाइप्ड क्लाइंट, होस्टेड फ़्लो और सैंडबॉक्स। पूरी गाइड पढ़ने के लिए खुली है, और सैंडबॉक्स कुंजियां अर्ली एक्सेस के साथ मिलती हैं।

बेस URLhttps://kyc.zoi.network

क्विकस्टार्ट

  1. 1

    सेशन बनाएं

    आपका बैकएंड अपनी पार्टनर कुंजी के साथ POST /v1/partner/sessions को कॉल करता है।

  2. 2

    फ़्लो चलाएं

    क्लाइंट टोकन SDK को दें, या verificationUrl ब्राउज़र में खोलें।

  3. 3

    नतीजा पाएं

    वेबहुक का हस्ताक्षर जांचें, फिर पहचान ID और उसकी स्थिति सेव करें।

shell
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
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 जो कदम चलाता है

  1. 01

    सेशन और बाक़ी ज़रूरी कदम पढ़ें।

    GET /v1/client/session

  2. 02

    सहमति दर्ज करें, साथ में चेहरे की तुलना के लिए अलग और वैकल्पिक चुनाव।

    POST /v1/client/consent

  3. 03

    ऐप और डिवाइस जांचें। नतीजा फ़ैसले का हिस्सा बनता है।

    POST /v1/client/attestation/challenge, POST /v1/client/attestation

  4. 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

  5. 05

    जब ऐप सिर की हरकतें दिखाए, तब सामने वाले कैमरे से एक छोटा वीडियो रिकॉर्ड करें।

    POST /v1/client/liveness/challenge, POST /v1/client/liveness

  6. 06

    निवास का देश भेजें, और एन्हांस्ड स्तर पर पता भी।

    POST /v1/client/profile

  7. 07

    सिर्फ़ पते की ज़रूरत होने पर: दस्तावेज़ अपलोड करें, फिर पते की पुष्टि करें। अगर पहचान दस्तावेज़ से पता पहले ही मिल गया हो, तो यह कदम छूट जाता है।

    POST /v1/client/address/document, POST /v1/client/address/confirm

  8. 08

    सिर्फ़ फ़ंड की ज़रूरत होने पर: जवाब सेव करें, पॉलिसी मांगे तो दस्तावेज़ जोड़ें, फिर कदम पूरा करें।

    POST /v1/client/funds/answers, POST /v1/client/funds/document, POST /v1/client/funds/remove, POST /v1/client/funds/complete

  9. 09

    दो सवालों के जवाब दें: क्या आप राजनीतिक रूप से प्रमुख व्यक्ति हैं, और क्या आप अपनी ओर से काम कर रहे हैं।

    POST /v1/client/declarations

  10. 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
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
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 स्कोप हो, तो कंप्लायंस भूमिका वाले सदस्य डैशबोर्ड में फ़ंड के स्रोत वाले दस्तावेज़ों के पेज देख सकते हैं। हर बार देखना लॉग होता है।
GET /v1/partner/sessions/:id
"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 दें। यह आपके रजिस्टर किए गए redirect origins में से किसी एक से शुरू होना चाहिए।

POST /v1/partner/sessions
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 खोलता है।

संदेश या redirect पैरामीटर पर कभी भरोसा न करें। सिर्फ़ अपने webhook या GET /v1/partner/sessions/:id पर भरोसा करें।

postMessage
// 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=processing

3. वेबव्यू सेटिंग

Android WebView

javaScriptEnabled और domStorageEnabled चालू करें और mediaPlaybackRequiresUserGesture को false करें। आपके ऐप को CAMERA अनुमति मिलने के बाद WebChromeClient में onPermissionRequest पर RESOURCE_VIDEO_CAPTURE की अनुमति दें। फ़ोटो वाले विकल्प के लिए फ़ाइल चुनने की सुविधा जोड़ें।

Kotlin
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 में कर सकते हैं।

Swift
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: NSCameraUsageDescription

React Native WebView

mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback और javaScriptEnabled सेट करें। आपके ऐप को कैमरा अनुमति चाहिए।

TSX
<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 के टाइप्ड क्लाइंट इस्तेमाल करें।

@zoi/id-sdk
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 होता है। कोई इवेंट तभी लागू करें जब उसका रिविज़न आपके रखे हुए से नया हो।
identity.suspended
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.processing
  • session.review
  • session.approved
  • session.rejected
  • session.resubmit
  • session.expired
  • session.cancelled
  • identity.updated
  • identity.suspended
  • identity.reinstated
  • identity.expired
  • identity.revoked

TypeScript में हस्ताक्षर जांचें

verify.ts
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 में हस्ताक्षर जांचें

verify.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
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/:id/fraud-reports
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/: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
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
कोडHTTPमतलब
bad_request400अनुरोध अनुबंध से मेल नहीं खाता।
unauthorized401कुंजी या टोकन नहीं है या ग़लत है।
token_expired401क्लाइंट टोकन की मियाद ख़त्म हो गई। अपने बैकएंड से नया बनाएं।
forbidden403यह कुंजी यह काम नहीं कर सकती।
not_found404ऐसा कोई सेशन या यूज़र नहीं है।
session_closed409सेशन अंतिम है। अब और अपलोड नहीं।
step_not_allowed409ग़लत क्रम, या अभी इस कदम की ज़रूरत नहीं।
revision_conflict409सेशन का रिविज़न बदल गया। फिर से पढ़ें।
idempotency_conflict409एक ही Idempotency-Key किसी दूसरे अनुरोध के साथ इस्तेमाल हुई।
challenge_expired410चुनौती की मियाद ख़त्म हो गई या पहले इस्तेमाल हो चुकी। नई मांगें।
payload_too_large413अपलोड बहुत बड़ा है।
unsupported_media_type415यह फ़ाइल प्रकार स्वीकार नहीं है।
rate_limited429बहुत ज़्यादा अनुरोध। बाद में फिर कोशिश करें।
internal500हमारी तरफ़ कुछ गड़बड़ हुई।
dependency_unavailable503जिस सेवा की हमें ज़रूरत है, वह बंद है। बाद में फिर कोशिश करें।

कारण कोड

जब दोबारा भेजना हो या अस्वीकार हो, तो सेशन में reasonCodes होते हैं। हर कोड के लिए अपना अनुवादित संदेश दिखाएं। यूज़र को कभी कच्चा कोड न दिखाएं।

  • consent_missing
  • consent_withdrawn
  • attestation_failed
  • document_expired
  • document_unreadable
  • document_not_supported
  • document_suspected_tampering
  • document_number_visible
  • chip_verification_failed
  • chip_proof_missing
  • chip_data_mismatch
  • mrz_invalid
  • photo_quality
  • face_not_found
  • face_mismatch
  • liveness_failed
  • underage
  • location_not_supported
  • residence_not_supported
  • sanctions_match
  • pep_review
  • duplicate_identity
  • possible_duplicate
  • declaration_requires_review
  • user_requested_review
  • reviewer_other
  • address_unreadable
  • address_document_too_old
  • address_document_not_accepted
  • address_name_mismatch
  • address_signature_required
  • funds_document_unreadable
  • funds_document_too_old
  • funds_document_not_accepted
  • funds_more_evidence
  • funds_evidence_rejected