> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# KYC API इंटीग्रेशन: पूरा फ़्लो और एंड-टू-एंड गाइड

> Didit REST API से एंड-टू-एंड इंटीग्रेशन: क्रेडेंशियल प्राप्त करें, सेशन बनाएँ, लिंक साझा करें, webhooks संभालें और KYC निर्णय मिनटों में पाएँ।

इस गाइड में REST API के जरिए Didit पहचान सत्यापन को इंटीग्रेट करने का पूरा प्रवाह दिया गया है: क्रेडेंशियल प्राप्त करना, सत्र बनाना, उसे उपयोगकर्ता के सामने प्रस्तुत करना और परिणाम प्राप्त करना।

***

## चरण 1: अपने क्रेडेंशियल प्राप्त करें

`api_key` प्राप्त करने के दो तरीके हैं:

### विकल्प A: प्रोग्रामैटिक रजिस्ट्रेशन (बिना ब्राउज़र)

CI/CD और AI एजेंट्स के लिए सबसे उपयुक्त। दो API कॉल: `POST /programmatic/register/` और फिर `POST /programmatic/verify-email/`, जो सीधे `api_key` लौटाती हैं।

→ पूरे प्रवाह, पासवर्ड नियमों, JWT की अवधि और लॉकआउट नीति के लिए [प्रोग्रामैटिक रजिस्ट्रेशन](https://docs.didit.me/hi/integration/programmatic-registration.md) देखें।

### विकल्प B: Didit कंसोल से

1. [Didit कंसोल](https://business.didit.me) पर जाएं।
2. अपना संगठन बनाएं या चुनें।
3. **सेटिंग्स** → **API और webhook** पर जाएं।
4. इन्हें कॉपी करें:
   * **API कुंजी**: हर सत्यापन कॉल पर `x-api-key` हेडर के रूप में उपयोग होती है।
   * **webhook गुप्त कुंजी**: webhook के HMAC हस्ताक्षरों की पुष्टि के लिए उपयोग होती है।

### एनवायरनमेंट वेरिएबल

```bash theme={null}
# Required
DIDIT_API_KEY=your_api_key_here
DIDIT_WEBHOOK_SECRET=your_webhook_secret_here

# Your workflow ID (from the Console)
DIDIT_WORKFLOW_ID=your_workflow_id_here
```

***

## चरण 2: वर्कफ़्लो बनाएं

सत्र बनाने से पहले आपको एक पहचान सत्यापन वर्कफ़्लो चाहिए। वर्कफ़्लो तय करता है कि उपयोगकर्ता किन सत्यापन चरणों से गुजरेंगे।

### कंसोल में बनाएं

1. [Didit कंसोल](https://business.didit.me) पर जाएं → **वर्कफ़्लो**
2. **वर्कफ़्लो बनाएं** पर क्लिक करें
3. एक बेस टेम्पलेट चुनें:

| टेम्पलेट | किसके लिए सबसे उपयुक्त |
| - | - |
| **KYC** | ID दस्तावेज़ के साथ पूर्ण पहचान सत्यापन |
| **अनुकूली आयु सत्यापन** | वैकल्पिक ID बैकअप के साथ आयु सत्यापन |
| **बायोमेट्रिक प्रमाणीकरण** | लौटने वाले उपयोगकर्ताओं का पुनः सत्यापन |
| **पता सत्यापन** | पते के प्रमाण का सत्यापन |

4. वैकल्पिक सुविधाएं जोड़ें:

| सुविधा | विवरण |
| - | - |
| NFC सत्यापन | पासपोर्ट/ID चिप का डेटा पढ़ें |
| लाइवनेस डिटेक्शन | सेल्फ़ी वीडियो से स्पूफिंग रोकें |
| चेहरा मिलान | सेल्फ़ी को दस्तावेज़ की फ़ोटो से मिलाएं |
| फ़ोन सत्यापन | SMS के ज़रिए फ़ोन नंबर सत्यापित करें |
| ईमेल सत्यापन | ईमेल पता सत्यापित करें |
| AML स्क्रीनिंग | प्रतिबंध सूचियों/PEP सूचियों से मिलान करें |
| डेटाबेस सत्यापन | सरकारी डेटाबेस से सत्यापित करें |
| डिवाइस और IP विश्लेषण | VPN, प्रॉक्सी और जियोलोकेशन का पता लगाएं |

5. अपना **वर्कफ़्लो ID** कॉपी करें

> [!NOTE]
>
> विस्तृत कॉन्फ़िगरेशन विकल्पों के लिए [वर्कफ़्लो दस्तावेज़ीकरण](https://docs.didit.me/hi/console/workflows.md) देखें।

***

## चरण 3: सत्यापन सत्र बनाएं

अपने उपयोगकर्ता के लिए सत्र बनाने हेतु API को कॉल करें।

### अनुरोध

```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "workflow_id": "your-workflow-id",
    "callback": "https://yourapp.com/verification-complete",
    "vendor_data": "user-123"
  }'
```

### अनुरोध पैरामीटर

| पैरामीटर | आवश्यक | विवरण |
| - | - | - |
| `workflow_id` | हाँ | कंसोल से आपका वर्कफ़्लो UUID। |
| `callback` | नहीं | सत्यापन के बाद उपयोगकर्ता को भेजा जाने वाला URL। Didit क्वेरी पैरामीटर के रूप में `verificationSessionId` और `status` जोड़ता है। कस्टम स्कीमें समर्थित हैं (जैसे `myapp://`)। छोड़े जाने पर वर्कफ़्लो में कॉन्फ़िगर किए गए कॉलबैक URL का उपयोग होता है। |
| `callback_method` | नहीं | रीडायरेक्ट कौन-सा डिवाइस संभालेगा: `initiator`, `completer`, या `both`। डिफ़ॉल्ट: `initiator`। |
| `vendor_data` | नहीं | आपका आंतरिक उपयोगकर्ता पहचानकर्ता, जो सहसंबंध के लिए webhook में वापस भेजा जाता है। |
| `metadata` | नहीं | सत्र के साथ संग्रहीत और webhook में वापस भेजा जाने वाला मनमाना JSON ऑब्जेक्ट। |
| `language` | नहीं | UI भाषा का संकेत (ISO 639-1, जैसे `en`, `es`)। छोड़े जाने पर ब्राउज़र की भाषा स्वतः पहचानी जाती है। |
| `contact_details` | नहीं | प्रीफ़िल और ईमेल सूचनाओं के लिए उपयोगकर्ता का ईमेल/फ़ोन। |
| `expected_details` | नहीं | सत्यापित डेटा के साथ क्रॉस-वैलिडेशन के लिए अपेक्षित उपयोगकर्ता विवरण। |
| `portrait_image` | शर्तानुसार | Base64-एन्कोडेड चेहरे की छवि (अधिकतम 2MB; JPEG, PNG, WebP, या TIFF)। बायोमेट्रिक प्रमाणीकरण / चेहरा मिलान-प्रथम वर्कफ़्लो के लिए संदर्भ चेहरा। यह तब वैकल्पिक है जब `vendor_data` वाले उपयोगकर्ता के पास पहले से संग्रहीत चेहरा हो (स्वीकृत लाइवनेस चेहरा, ePassport फ़ोटो, पहचान दस्तावेज़ का पोर्ट्रेट, या पंजीकृत प्रोफ़ाइल चेहरा): Didit उसे स्वतः उपयोग करता है; यदि इनमें से कोई भी उपलब्ध न हो तो `400` मिलता है। |
| `sandbox_scenario` | नहीं | केवल सैंडबॉक्स के लिए परिदृश्य स्लग (जैसे `approve`, `decline_aml_hit`), जो सत्र को मैजिक इनपुट से स्वतः भरता है। |

### सभी विकल्पों वाला पूरा उदाहरण

```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "workflow_id": "11111111-2222-3333-4444-555555555555",
    "callback": "https://yourapp.com/verification-complete",
    "vendor_data": "user-123",
    "metadata": {
      "plan": "premium",
      "signup_source": "mobile-app"
    },
    "contact_details": {
      "email": "alex.sample@example.com",
      "email_lang": "en",
      "send_notification_emails": true,
      "phone": "+15550101000"
    },
    "expected_details": {
      "first_name": "John",
      "last_name": "Doe",
      "date_of_birth": "1990-01-01",
      "expected_document_types": ["P", "ID"]
    }
  }'
```

> [!NOTE]
>
> `expected_details.expected_document_types` पहचान सत्यापन चरण को विशिष्ट दस्तावेज़ प्रकारों तक सीमित करता है। दस्तावेज़ चयन स्क्रीन केवल अनुरोधित प्रकार दिखाती है। अनुमत मान: `P` (पासपोर्ट), `ID` (राष्ट्रीय पहचान पत्र), `DL` (ड्राइविंग लाइसेंस), `RP` (निवास परमिट), `HIC` (स्वास्थ्य बीमा कार्ड), `TC` (टैक्स कार्ड), `SSC` (सामाजिक सुरक्षा कार्ड)। मान केस-इनसेंसिटिव हैं और डुप्लीकेट हटा दिए जाते हैं; अज्ञात मानों पर `400` मिलता है। पूरे स्कीमा के लिए [सत्र बनाने का API संदर्भ](https://docs.didit.me/hi/sessions-api/create-session.md) देखें।

### रिस्पॉन्स

`201 Created`:

```json theme={null}
{
  "session_id": "11111111-2222-3333-4444-555555555555",
  "session_number": 43762,
  "session_token": "3FaJ9wLqX2Mz",
  "url": "https://verify.didit.me/session/3FaJ9wLqX2Mz",
  "vendor_data": "user-123",
  "metadata": {
    "plan": "premium",
    "signup_source": "mobile-app"
  },
  "status": "Not Started",
  "workflow_id": "11111111-2222-3333-4444-555555555555",
  "workflow_version": 3,
  "callback": "https://yourapp.com/verification-complete"
}
```

### मुख्य रिस्पॉन्स फ़ील्ड

| फ़ील्ड | विवरण |
| - | - |
| `session_id` | इस सत्र के लिए अद्वितीय UUID। इसका उपयोग `GET /v3/session/{sessionId}/decision/` और webhook के लिए करें। |
| `session_number` | कंसोल में दिखाई देने वाली मानव-पठनीय अनुक्रम संख्या। |
| `session_token` | 12 वर्णों का URL-safe टोकन, जो अंतिम उपयोगकर्ता को होस्टेड फ़्लो खोलने की अनुमति देता है और नेटिव SDKs को इनिशियलाइज़ करता है (वेब SDK `url` लेता है)। इसे गोपनीय मानें। |
| `url` | उपयोगकर्ता को रीडायरेक्ट करने या iframe में एम्बेड करने के लिए होस्टेड सत्यापन URL। (फ़ील्ड का नाम `url` है, `verification_url` नहीं।) जब अनुरोध में `language` शामिल हो, तो URL में भाषा पथ सेगमेंट होता है, उदाहरण के लिए `https://verify.didit.me/en/session/3FaJ9wLqX2Mz`। |
| `status` | वर्तमान स्थिति। नए बनाए गए सत्र के लिए `"Not Started"`। |
| `workflow_id` | स्थिर वर्कफ़्लो पहचानकर्ता जिस पर सत्र चलता है। |
| `workflow_version` | प्रकाशित वर्कफ़्लो संस्करण जिस पर निर्माण के समय सत्र को पिन किया गया था। |

> [!NOTE]
>
> **आइडेम्पोटेंसी.** जब `vendor_data` दिया गया हो और उसी `vendor_data` वाला कोई अधूरा सत्र (`Not Started`, `In Progress`, `Resubmitted`, या `Awaiting User`) वर्कफ़्लो के नवीनतम प्रकाशित संस्करण पर पहले से मौजूद हो, तो डुप्लीकेट बनाने के बजाय वही मौजूदा सत्र लौटाया जाता है (अब भी `201` के साथ)। इसका `callback` और `metadata` नए मानों पर अपडेट हो जाते हैं, और `status` मौजूदा सत्र की स्थिति दिखाता है।

> [!NOTE]
>
> **`x-api-key` के बिना अनुरोध करने पर `403` मिलता है**, जिसकी बॉडी वही होती है जो वैध कुंजी पर अनुमति न होने पर मिलती है: `{"detail": "You do not have permission to perform this action."}`। गलत, रद्द या समाप्त कुंजी पर `401` मिलता है। अपर्याप्त क्रेडिट होने पर `400` मिलता है। पूरे दस्तावेज़ के लिए [सत्र बनाने का API संदर्भ](https://docs.didit.me/hi/sessions-api/create-session.md) देखें।

***

## चरण 4: उपयोगकर्ता को सत्यापन दिखाएं

अपने उपयोगकर्ता को सत्यापन फ़्लो दिखाने का तरीका चुनें:

### विकल्प A: नेटिव SDK (मोबाइल के लिए अनुशंसित)

कैमरा, NFC और बायोमेट्रिक इंटीग्रेशन के लिए नेटिव SDKs के साथ `session_token` का उपयोग करें।

→ [iOS SDK](https://docs.didit.me/hi/integration/native-sdks/ios-sdk.md) · [Android SDK](https://docs.didit.me/hi/integration/native-sdks/android-sdk.md) · [React Native](https://docs.didit.me/hi/integration/native-sdks/react-native-sdk.md) · [Flutter](https://docs.didit.me/hi/integration/native-sdks/flutter-sdk.md)

### विकल्प B: InContext iframe (वेब के लिए अनुशंसित)

चरण 3 में मिले `url` का उपयोग करके सत्यापन को अपने पेज में एम्बेड करें।

```html theme={null}
<iframe
  src="{url}"
  style="width: 100%; height: 700px; border: none;"
  allow="camera; microphone; geolocation; clipboard-write; fullscreen; autoplay; encrypted-media; digital-credentials-get"
></iframe>
```

→ [InContext iframe](https://docs.didit.me/hi/integration/web-sdks/incontext-iframe.md)

### विकल्प C: रीडायरेक्ट

उपयोगकर्ता को सत्यापन URL पर रीडायरेक्ट करें।

```javascript theme={null}
window.location.href = session.url;
```

→ [वेब रीडायरेक्ट](https://docs.didit.me/hi/integration/web-sdks/web-redirect.md)

### विकल्प D: मोबाइल WebView

उन मोबाइल ऐप्स के लिए जिनमें नेटिव SDK नहीं है।

```jsx theme={null}
// React Native
<WebView source={{ uri: session.url }} />
```

→ [iOS/Android में WebView](https://docs.didit.me/hi/integration/web-sdks/webview-in-ios-android.md)

***

## चरण 5: परिणाम प्राप्त करें

### Webhook के माध्यम से (अनुशंसित)

जब सत्यापन की स्थिति बदलती है, तब webhooks वास्तविक समय में आपके सर्वर को सूचित करते हैं।

**सेटअप:**

1. [कंसोल](https://business.didit.me) (**API & Webhooks**) में या [`POST /v3/webhook/destinations/`](https://docs.didit.me/hi/management-api/webhook/create-destination) के माध्यम से, `webhook_version: "v3"` और एक `subscribed_events` array (जैसे `["status.updated"]`) के साथ webhook गंतव्य बनाएँ
2. निर्माण के समय लौटाए गए `secret_shared_key` को सुरक्षित रखें: यह सभी हस्ताक्षर रूपों के लिए HMAC सीक्रेट है
3. अपने सर्वर पर एक webhook endpoint लागू करें

**webhook payload का उदाहरण:**

`decision` के भीतर, हर फ़ीचर का परिणाम एक **बहुवचन ऐरे** के रूप में मिलता है (`id_verifications[]`, `nfc_verifications[]`, `liveness_checks[]`, `face_matches[]`, …), और workflow के हर नोड के लिए एक प्रविष्टि होती है। यह V3 अनुबंध है और इसमें बदलाव नहीं होता: कभी भी एकवचन `nfc`, `id_verification` या `liveness` कुंजी के आधार पर कोड न लिखें।

```json theme={null}
{
  "event_id": "9c0c8b8a-1111-4222-9333-444444444444",
  "webhook_type": "status.updated",
  "timestamp": 1774970000,
  "created_at": 1774969994,
  "application_id": "11111111-2222-3333-4444-555555555555",
  "environment": "live",
  "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "Approved",
  "workflow_id": "22222222-3333-4444-5555-666666666666",
  "workflow_version": 4,
  "vendor_data": "user-123",
  "metadata": { "plan": "premium" },
  "decision": {
    "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "status": "Approved",
    "id_verifications": [
      { "node_id": "id_verification_1", "status": "Approved", "document_type": "Passport", "warnings": [] }
    ],
    "liveness_checks": [
      { "node_id": "liveness_1", "status": "Approved", "method": "PASSIVE", "score": 95.4, "warnings": [] }
    ],
    "face_matches": [
      { "node_id": "face_match_1", "status": "Approved", "score": 96.1, "warnings": [] }
    ],
    "reviews": []
  }
}
```

`decision` ऑब्जेक्ट केवल तभी मौजूद होता है जब `status` `Approved`, `Declined`, `In Review` या `Abandoned` हो (`Resubmitted` इवेंट में इसके बजाय `resubmit_info` होता है)। Didit एक ही `event_id` को पुनः प्रयासों और fan-out गंतव्यों में दोबारा इस्तेमाल करता है, इसलिए idempotency के लिए इसी फ़ील्ड पर अपनी कुंजी रखें। हर बहुवचन array और फ़ील्ड के लिए [डेटा मॉडल](https://docs.didit.me/hi/reference/data-models.md) देखें।

**हस्ताक्षर सत्यापित करें (X-Signature-V2, अनुशंसित):**

`X-Signature-V2` JSON body के canonical re-encoding पर HMAC-SHA256 है: पूर्ण-मान वाले floats को integers में छोटा किया जाता है, keys पुनरावर्ती रूप से sort की जाती हैं, separators compact होते हैं, और Unicode सुरक्षित रखा जाता है।

```javascript theme={null}
const crypto = require("crypto");

// Match Didit's float normalisation: whole-valued floats serialise as ints.
function shortenFloats(data) {
  if (Array.isArray(data)) return data.map(shortenFloats);
  if (data !== null && typeof data === "object") {
    return Object.fromEntries(
      Object.entries(data).map(([key, value]) => [key, shortenFloats(value)])
    );
  }
  return data;
}

// Sort object keys recursively before re-stringifying.
function sortKeys(obj) {
  if (Array.isArray(obj)) return obj.map(sortKeys);
  if (obj !== null && typeof obj === "object") {
    return Object.keys(obj).sort().reduce((acc, key) => {
      acc[key] = sortKeys(obj[key]);
      return acc;
    }, {});
  }
  return obj;
}

function verifyWebhook(rawBody, signatureV2, timestamp, secret) {
  // 1. Reject anything older than 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) return false;

  // 2. Recompute HMAC-SHA256 against the canonical JSON Didit signs
  const canonical = JSON.stringify(sortKeys(shortenFloats(JSON.parse(rawBody))));
  const expected = crypto.createHmac("sha256", secret).update(canonical, "utf8").digest("hex");

  // 3. Constant-time compare
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(signatureV2, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

→ [webhooks का पूरा दस्तावेज़](https://docs.didit.me/hi/integration/webhooks.md), जिसमें raw-bytes वाला `X-Signature`, अप्रचलित envelope-only `X-Signature-Simple` और कौन सा चुनें, यह भी शामिल है। Didit विफल डिलीवरी (`5xx`, `404`, timeout) को अधिकतम 2 बार पुनः प्रयास करता है (लगभग 1 मिनट, फिर लगभग 4 मिनट बाद), और **5 सेकंड** के बाद timeout हो जाता है, इसलिए `2xx` तुरंत लौटाएँ।

### API के माध्यम से (वैकल्पिक)

किसी भी समय वही decision payload प्राप्त करें:

```bash theme={null}
curl https://verification.didit.me/v3/session/{session_id}/decision/ \
  -H "x-api-key: YOUR_API_KEY"
```

प्रतिक्रिया का ढाँचा (बहुवचन arrays) webhook payload के भीतर `decision` ऑब्जेक्ट के समान है, और यह endpoint सत्र के जीवनचक्र के किसी भी बिंदु पर कॉल किया जा सकता है। real-time अपडेट के लिए webhooks इस्तेमाल करें और API कॉल्स को backfill, audit और rebuilds के लिए रखें। `/decision/` endpoint प्रति API कुंजी प्रति मिनट 600 GET अनुरोधों तक सीमित है (देखें [रेट लिमिट](https://docs.didit.me/hi/integration/rate-limiting.md))। त्रुटि व्यवहार: अज्ञात `session_id` पर `{"detail": "Not found."}` के साथ `404` लौटता है; प्रमाणीकरण विफलताएँ `403` के रूप में दिखती हैं, और यह endpoint कभी `401` नहीं लौटाता। प्रतिक्रिया में मीडिया URLs अल्पकालिक presigned links हैं, इसलिए इन्हें स्टोर करने के बजाय तुरंत प्राप्त करें।

→ [सत्र प्राप्त करने का API संदर्भ](https://docs.didit.me/hi/sessions-api/retrieve-session.md)

***

## पूर्ण कोड उदाहरण

### Node.js / Express

```javascript theme={null}
const express = require("express");
const crypto = require("crypto");
const app = express();

// Create verification session
app.post("/api/verify", async (req, res) => {
  const { userId } = req.body;

  const response = await fetch("https://verification.didit.me/v3/session/", {
    method: "POST",
    headers: {
      "x-api-key": process.env.DIDIT_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      workflow_id: process.env.DIDIT_WORKFLOW_ID,
      callback: `${process.env.APP_URL}/verification-complete`,
      vendor_data: userId,
    }),
  });

  const session = await response.json();

  // Store session_id associated with user
  await db.users.update({
    where: { id: userId },
    data: { verificationSessionId: session.session_id },
  });

  res.json({
    verificationUrl: session.url,             // hosted verification URL
    sessionToken: session.session_token,      // for native/web SDKs
  });
});

// Handle webhook — uses verifyWebhook / shortenFloats / sortKeys from Step 5
app.post(
  "/api/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.get("X-Signature-V2");
    const timestamp = req.get("X-Timestamp");

    if (
      !verifyWebhook(
        req.body.toString("utf8"),
        signature,
        timestamp,
        process.env.DIDIT_WEBHOOK_SECRET,
      )
    ) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const payload = JSON.parse(req.body);
    const { event_id, session_id, status, vendor_data } = payload;

    // Idempotency: the same event_id is reused on retries and fan-out.
    // Skip events you have already processed (e.g. a processed_events table).

    // Update user's verification status (status values are case-sensitive strings)
    if (status === "Approved") {
      db.users.update({
        where: { id: vendor_data },
        data: { isVerified: true, verifiedAt: new Date() },
      });
    }

    // Return 2xx quickly (Didit's delivery timeout is 5s); do heavy work asynchronously
    res.json({ received: true });
  },
);

app.listen(3000);
```

### Python / FastAPI

```python theme={null}
from fastapi import FastAPI, Request, HTTPException
import httpx
import hashlib
import hmac
import json
import os
import time

app = FastAPI()

@app.post("/api/verify")
async def create_verification(user_id: str):
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://verification.didit.me/v3/session/",
            headers={
                "x-api-key": os.environ["DIDIT_API_KEY"],
                "Content-Type": "application/json"
            },
            json={
                "workflow_id": os.environ["DIDIT_WORKFLOW_ID"],
                "callback": f"{os.environ['APP_URL']}/verification-complete",
                "vendor_data": user_id
            }
        )

    session = response.json()
    return {
        "verification_url": session["url"],            # hosted verification URL
        "session_token": session["session_token"],     # for native/web SDKs
    }

def shorten_floats(data):
    """Match Didit: whole-valued floats serialise as ints."""
    if isinstance(data, dict):
        return {k: shorten_floats(v) for k, v in data.items()}
    if isinstance(data, list):
        return [shorten_floats(x) for x in data]
    if isinstance(data, float) and data.is_integer():
        return int(data)
    return data

@app.post("/api/webhook")
async def handle_webhook(request: Request):
    payload = await request.json()
    signature = request.headers.get("X-Signature-V2")
    timestamp = request.headers.get("X-Timestamp")

    # Verify timestamp freshness
    if not signature or not timestamp or abs(int(time.time()) - int(timestamp)) > 300:
        raise HTTPException(status_code=401, detail="Request too old")

    # Recompute Didit's V2 canonical JSON: shortened floats, sorted keys,
    # compact separators, Unicode preserved (ensure_ascii=False)
    canonical = json.dumps(
        shorten_floats(payload),
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
    )
    expected = hmac.new(
        os.environ["DIDIT_WEBHOOK_SECRET"].encode("utf-8"),
        canonical.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Idempotency: dedupe on payload["event_id"] (reused on retries)

    if payload["status"] == "Approved":
        # Update user in database
        pass

    return {"received": True}
```

***

## सत्र की स्थितियाँ

ये `status` के 10 संभावित मान हैं, जो बिल्कुल इसी रूप में और case-sensitive strings के रूप में लिखे जाते हैं (ध्यान दें कि `Kyc Expired` में केवल एक बड़ा `K` है):

| स्थिति | विवरण |
| - | - |
| `Not Started` | सत्र बनाया गया है, लेकिन उपयोगकर्ता ने अभी लिंक नहीं खोला है। |
| `In Progress` | उपयोगकर्ता अभी सत्यापन पूरा कर रहा है। |
| `In Review` | सत्यापन को मैन्युअल समीक्षा के लिए चिह्नित किया गया है। |
| `Approved` | सभी जाँचें उत्तीर्ण हुईं। |
| `Declined` | एक या अधिक जाँचें विफल रहीं। |
| `Resubmitted` | समीक्षक ने कुछ विशिष्ट चरणों को दोबारा करने का अनुरोध किया। |
| `Awaiting User` | KYB पैरेंट सत्र, चाइल्ड KYC पक्षों के सत्यापन पूरे होने की प्रतीक्षा कर रहा है। |
| `Abandoned` | उपयोगकर्ता ने शुरू किया, लेकिन निर्धारित समय में पूरा नहीं किया। |
| `Expired` | उपयोगकर्ता के लिंक खोलने से पहले सत्र का समय समाप्त हो गया। |
| `Kyc Expired` | पहले स्वीकृत सत्र, जिसके दस्तावेज़ की समाप्ति तिथि निकल चुकी है। |

पूरा जीवनचक्र, संक्रमण और Mermaid आरेख देखने के लिए [सत्यापन स्थितियाँ](https://docs.didit.me/hi/integration/verification-statuses.md) देखें।

***

## अगले चरण

1. **अपनी इंटीग्रेशन विधि चुनें**:
   * मोबाइल ऐप → [नेटिव SDK](https://docs.didit.me/hi/integration/native-sdks/overview.md)
   * वेब ऐप → [वेब SDK](https://docs.didit.me/hi/integration/web-sdks/overview.md)
2. **webhooks कॉन्फ़िगर करें**: [Webhooks](https://docs.didit.me/hi/integration/webhooks.md)
3. **क्षमता की योजना बनाएँ**: [रेट लिमिट](https://docs.didit.me/hi/integration/rate-limiting.md)
4. **हर स्थिति को संभालें**: [सत्यापन स्थितियाँ](https://docs.didit.me/hi/integration/verification-statuses.md)
5. **अपना वर्कफ़्लो अनुकूलित करें**: [वर्कफ़्लो](https://docs.didit.me/hi/console/workflows.md)
6. **जाँचें कि API पहुँच योग्य है**: [Healthcheck](https://docs.didit.me/hi/api-reference/healthcheck.md)

## मदद चाहिए?

* **सहायता**: [support@didit.me](mailto:support@didit.me)
* **प्रोग्रामेटिक ऑनबोर्डिंग**: [प्रोग्रामेटिक पंजीकरण](https://docs.didit.me/hi/integration/programmatic-registration.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.