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

# Integració de l'API KYC: flux complet de principi a fi

> Integració completa de l'API REST de Didit: obtén credencials, crea una sessió, comparteix l'enllaç, gestiona webhooks i recupera decisions KYC en minuts.

Esta guia us explica el flux complet per integrar la verificació d'identitat de Didit mitjançant l'API REST: obtenir les credencials, crear una sessió, presentar-la a l'usuari i rebre els resultats.

***

## Pas 1: obtén les teves credencials

Hi ha dues maneres d'obtenir una `api_key`:

### Opció A: registre programàtic (sense navegador)

Ideal per a CI/CD i agents d'IA. Són dues crides a l'API: `POST /programmatic/register/` i després `POST /programmatic/verify-email/`, que retornen la `api_key` en línia.

→ Consulta [Registre programàtic](https://docs.didit.me/ca/integration/programmatic-registration.md) per al flux complet, les regles de contrasenya, la durada del JWT i la política de bloqueig.

### Opció B: des de la Didit Console

1. Ves a la [Didit Console](https://business.didit.me).
2. Crea o selecciona la teva organització.
3. Navega a **Configuració** → **API i Webhooks**.
4. Copia:
   * **Clau d'API**: s'utilitza com a capçalera `x-api-key` en cada crida de verificació.
   * **Clau secreta del webhook**: s'utilitza per verificar les signatures HMAC dels webhooks.

### Variables d'entorn

```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
```

***

## Pas 2: crea un flux de treball

Abans de crear sessions, necessites un flux de treball de verificació. Els fluxos de treball defineixen els passos de verificació pels quals passen els usuaris.

### Crea'l a la Console

1. Ves a la [Didit Console](https://business.didit.me) → **Fluxos de treball**
2. Fes clic a **Crear flux de treball**
3. Tria una plantilla base:

| Plantilla | Ideal per |
| - | - |
| **KYC** | Verificació d'identitat completa amb document d'identitat |
| **Verificació d'edat adaptativa** | Verificació d'edat amb document d'identitat com a alternativa |
| **Autenticació biomètrica** | Tornar a verificar usuaris que tornen i que ja han estat verificats |
| **Verificació d'adreça** | Verificació de comprovant de domicili |

4. Afegeix funcionalitats opcionals:

| Funcionalitat | Descripció |
| - | - |
| Verificació NFC | Llegeix les dades del xip del passaport o del document d'identitat |
| Prova de vida | Evita la suplantació amb un vídeo selfie |
| Coincidència facial | Compara el selfie amb la foto del document |
| Verificació de telèfon | Verifica el número de telèfon per SMS |
| Verificació de correu electrònic | Verifica l'adreça de correu electrònic |
| Cribratge AML | Comprova les llistes de sancions i de PEP |
| Validació en bases de dades | Verifica les dades amb bases de dades governamentals |
| Anàlisi de dispositiu i IP | Detecta VPN, proxies i geolocalització |

5. Copia el **ID del flux de treball**

> [!NOTE]
>
> Consulta la [documentació dels fluxos de treball](https://docs.didit.me/ca/console/workflows.md) per a les opcions de configuració detallades.

***

## Pas 3: Crea una sessió de verificació

Crida l'API per crear una sessió per a l'usuari.

### Sol·licitud

```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"
  }'
```

### Paràmetres de la sol·licitud

| Paràmetre | Obligatori | Descripció |
| - | - | - |
| `workflow_id` | Sí | L'UUID del teu flux de treball des de la Console. |
| `callback` | No | URL a la qual s'envia l'usuari després de la verificació. Didit afegeix `verificationSessionId` i `status` com a paràmetres de consulta. Admet esquemes personalitzats (p. ex., `myapp://`). Si s'omet, s'usa la URL de callback configurada al flux de treball. |
| `callback_method` | No | Quin dispositiu gestiona la redirecció: `initiator`, `completer` o `both`. Valor per defecte: `initiator`. |
| `vendor_data` | No | El teu identificador intern d'usuari, que es retorna als webhooks per correlacionar-lo. |
| `metadata` | No | Objecte JSON arbitrari que s'emmagatzema amb la sessió i es retorna als webhooks. |
| `language` | No | Indicació de l'idioma de la interfície (ISO 639-1, p. ex., `en`, `es`). Si s'omet, es detecta automàticament l'idioma del navegador. |
| `contact_details` | No | Correu electrònic o telèfon de l'usuari per a l'emplenament previ i les notificacions per correu electrònic. |
| `expected_details` | No | Dades esperades de l'usuari per a la validació creuada amb les dades verificades. |
| `portrait_image` | Condicional | Imatge facial codificada en Base64 (màx. 2 MB; JPEG, PNG, WebP o TIFF). Imatge de referència per a l'Autenticació biomètrica o per als fluxos de treball que fan primer la Coincidència facial. És opcional quan l'usuari de `vendor_data` ja té una cara emmagatzemada (cara de prova de vida aprovada, foto del passaport electrònic, retrat del document d'identitat o cara del perfil registrat): Didit la reutilitza automàticament; `400` si no n'hi ha cap de disponible. |
| `sandbox_scenario` | No | Slug d'escenari només per a sandbox (p. ex., `approve`, `decline_aml_hit`) que omple automàticament la sessió amb entrades de prova. |

### Exemple complet amb totes les opcions

```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` restringeix el pas de verificació d'identitat a tipus de document concrets: la pantalla de selecció de documents només mostra els tipus sol·licitats. Valors permesos: `P` (passaport), `ID` (document nacional d'identitat), `DL` (permís de conducció), `RP` (permís de residència), `HIC` (targeta d'assegurança de salut), `TC` (targeta fiscal) i `SSC` (targeta de la seguretat social). Els valors no distingeixen majúscules i minúscules i es deduplican; els valors desconeguts retornen `400`. Consulta la [referència de creació de la sessió](https://docs.didit.me/ca/sessions-api/create-session.md) per veure l'esquema complet.

### Resposta

`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"
}
```

### Camps clau de la resposta

| Camp | Descripció |
| - | - |
| `session_id` | UUID únic d'aquesta sessió. Fes-lo servir per a `GET /v3/session/{sessionId}/decision/` i els webhooks. |
| `session_number` | Número de seqüència llegible per a les persones que es mostra a la consola de Didit. |
| `session_token` | Token de 12 caràcters segur per a URL que autoritza l'usuari final a obrir el flux allotjat i inicialitza els SDK natius (l'SDK web utilitza `url` en lloc d'això). Tracta'l com un secret. |
| `url` | URL de verificació allotjada a la qual redirigir l'usuari o incrustar en un iframe. (El nom del camp és `url`, no `verification_url`.) Quan la sol·licitud inclou `language`, la URL conté un segment de ruta d'idioma, p. ex., `https://verify.didit.me/en/session/3FaJ9wLqX2Mz`. |
| `status` | Estat actual: `"Not Started"` per a una sessió recentment creada. |
| `workflow_id` | Identificador estable del flux de treball en què s'executa la sessió. |
| `workflow_version` | Versió publicada del flux de treball a la qual la sessió es va fixar en el moment de la creació. |

> [!NOTE]
>
> **Idempotència.** Quan es proporciona `vendor_data` i ja existeix a la versió publicada més recent del flux de treball una sessió no finalitzada (`Not Started`, `In Progress`, `Resubmitted` o `Awaiting User`) amb el mateix `vendor_data`, es retorna aquesta sessió existent (encara amb `201`) en lloc de crear-ne un duplicat. El seu `callback` i `metadata` s'actualitzen amb els nous valors, i `status` reflecteix l'estat de la sessió existent.

> [!NOTE]
>
> **Si falta `x-api-key`, es retorna `403`** amb `{"detail": "You do not have permission to perform this action."}`, el mateix cos que una clau vàlida sense permís. Una clau incorrecta, revocada o caducada retorna `401`. Els crèdits insuficients retornen `400`. Consulta [Referència de l'API de creació de la sessió](https://docs.didit.me/ca/sessions-api/create-session.md) per a la documentació completa.

***

## Pas 4: Presenta la verificació a l'usuari

Tria com presentar el flux de verificació a l'usuari:

### Opció A: SDK natiu (recomanat per a mòbil)

Fes servir el `session_token` amb els SDK natius per a la integració de la càmera, NFC i biometria.

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

### Opció B: iframe InContext (recomanat per a web)

Incrusta la verificació a la teva pàgina utilitzant la `url` retornada al pas 3.

```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>
```

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

### Opció C: Redirecció

Redirigeix l'usuari a la URL de verificació.

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

→ [Redirecció web](https://docs.didit.me/ca/integration/web-sdks/web-redirect.md)

### Opció D: WebView mòbil

Per a aplicacions mòbils sense SDK natiu.

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

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

***

## Pas 5: Rebre els resultats

### Mitjançant webhook (recomanat)

Els webhooks notifiquen el vostre servidor en temps real cada vegada que canvia l'estat de la verificació.

**Configuració:**

1. Creeu una destinació de webhook a la [Consola](https://business.didit.me) (**API i Webhooks**) o mitjançant [`POST /v3/webhook/destinations/`](https://docs.didit.me/ca/management-api/webhook/create-destination) amb `webhook_version: "v3"` i un array `subscribed_events` (p. ex., `["status.updated"]`)
2. Deseu el `secret_shared_key` que es retorna en crear-la: és el secret HMAC per a totes les variants de signatura
3. Implementeu un endpoint de webhook al vostre servidor

**Exemple de payload de webhook:**

Dins de `decision`, cada resultat per funcionalitat s'entrega com a **array en plural** (`id_verifications[]`, `nfc_verifications[]`, `liveness_checks[]`, `face_matches[]`, …): una entrada per cada node del flux de treball. Aquest és el contracte de la V3 i és inamovible: mai no programeu contra les claus en singular `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": []
  }
}
```

L'objecte `decision` només està present quan `status` és `Approved`, `Declined`, `In Review` o `Abandoned` (un esdeveniment `Resubmitted` porta `resubmit_info` en lloc d'això). Didit reutilitza el mateix `event_id` en els reintents i les destinacions de distribució múltiple: utilitzeu-lo com a clau d'idempotència. Consulteu [Models de dades](https://docs.didit.me/ca/reference/data-models.md) per a tots els arrays en plural i camps.

**Verifiqueu la signatura (X-Signature-V2, recomanada):**

`X-Signature-V2` és HMAC-SHA256 sobre una recodificació canònica del cos JSON: els floats amb valor enter es redueixen a enters, les claus s'ordenen de manera recursiva, els separadors són compactes i Unicode es preserva.

```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);
}
```

→ [Documentació completa dels webhooks](https://docs.didit.me/ca/integration/webhooks.md), inclosa la `X-Signature` sobre bytes bruts, la `X-Signature-Simple` obsoleta (només sobre l'envoltori) i com triar-ne una. Didit reintenta els lliuraments fallits (`5xx`, `404`, temps d'espera) fins a 2 vegades (\~1 min, després \~4 min) i el temps d'espera és de **5 segons**: retorneu `2xx` ràpidament.

### Mitjançant API (opcional)

Obteniu el mateix payload de decisió en qualsevol moment:

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

La forma de la resposta (arrays en plural) és idèntica a l'objecte `decision` dins del payload del webhook, i l'endpoint es pot cridar en qualsevol punt del cicle de vida de la sessió. Utilitzeu els webhooks per a les actualitzacions en temps real i reserveu les crides a l'API per a la recuperació de dades, l'auditoria i les reconstruccions: l'endpoint `/decision/` té un límit de 600 sol·licituds GET per minut per clau d'API (consulteu [Límit de freqüència](https://docs.didit.me/ca/integration/rate-limiting.md)). Semàntica d'errors: un `session_id` desconegut retorna `404` amb `{"detail": "Not found."}`; els errors d'autenticació apareixen com a `403`; aquest endpoint mai no retorna `401`. Les URL de mitjans de la resposta són enllaços prefirmats de vida curta: obteniu-les de seguida en lloc de persistir-les.

→ [Referència de l'API per recuperar la sessió](https://docs.didit.me/ca/sessions-api/retrieve-session.md)

***

## Exemples de codi complets

### 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}
```

***

## Estats de la sessió

Aquests són els 10 valors possibles de `status`: cadenes exactes i sensibles a majúscules (fixeu-vos que `Kyc Expired` té una sola `K` majúscula):

| Estat | Descripció |
| - | - |
| `Not Started` | Sessió creada però l'usuari encara no ha obert l'enllaç. |
| `In Progress` | L'usuari està completant la verificació. |
| `In Review` | Verificació marcada per a revisió manual. |
| `Approved` | Totes les comprovacions han estat satisfactòries. |
| `Declined` | Una o més comprovacions han fallat. |
| `Resubmitted` | El revisor ha sol·licitat que es tornin a fer passos concrets. |
| `Awaiting User` | Sessió principal de KYB a l'espera que les parts KYC dependents acabin. |
| `Abandoned` | L'usuari ha començat però no ha acabat dins del temps assignat. |
| `Expired` | La sessió ha caducat abans que l'usuari obrís l'enllaç. |
| `Kyc Expired` | Sessió aprovada anteriorment el document de la qual ha superat la data de caducitat. |

Consulteu [Estats de verificació](https://docs.didit.me/ca/integration/verification-statuses.md) per al cicle de vida complet, les transicions i el diagrama de Mermaid.

***

## Següents passos

1. **Trieu el vostre mètode d'integració**:
   * Aplicacions mòbils → [SDK natius](https://docs.didit.me/ca/integration/native-sdks/overview.md)
   * Aplicacions web → [SDK web](https://docs.didit.me/ca/integration/web-sdks/overview.md)
2. **Configureu els webhooks**: [Webhooks](https://docs.didit.me/ca/integration/webhooks.md)
3. **Planifiqueu la capacitat**: [Límit de freqüència](https://docs.didit.me/ca/integration/rate-limiting.md)
4. **Gestioneu tots els estats**: [Estats de verificació](https://docs.didit.me/ca/integration/verification-statuses.md)
5. **Personalitzeu el vostre flux de treball**: [Fluxos de treball](https://docs.didit.me/ca/console/workflows.md)
6. **Verifiqueu que l'API és accessible**: [Comprovació de l'estat](https://docs.didit.me/ca/api-reference/healthcheck.md)

## Necessites ajuda?

* **Suport**: [support@didit.me](mailto:support@didit.me)
* **Alta programàtica**: [Registre programàtic](https://docs.didit.me/ca/integration/programmatic-registration.md)


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