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

# Integrazione API KYC: guida al flusso completo end-to-end

> Integrazione end-to-end con l'API REST di Didit: ottieni le credenziali, crea una sessione, condividi il link, gestisci i webhook e recupera le decisioni KYC.

Questa guida ti accompagna nel flusso completo per integrare la verifica dell'identità Didit tramite la REST API: ottieni le credenziali, crea una sessione, presentala all'utente e ricevi i risultati.

***

## Passaggio 1: Ottieni le tue credenziali

Hai due modi per ottenere una `api_key`:

### Opzione A: Registrazione programmatica (senza browser)

Ideale per CI/CD e agenti AI. Due chiamate API, `POST /programmatic/register/` e poi `POST /programmatic/verify-email/`, restituiscono la `api_key` direttamente nella risposta.

→ Vedi [Registrazione programmatica](https://docs.didit.me/it/integration/programmatic-registration.md) per il flusso completo, le regole sulle password, la durata del JWT e la politica di blocco.

### Opzione B: Dalla Didit Console

1. Vai alla [Didit Console](https://business.didit.me).
2. Crea o seleziona la tua organizzazione.
3. Vai su **Settings** → **API & Webhooks**.
4. Copia:
   * **API Key**: viene usata come intestazione `x-api-key` in ogni chiamata di verifica.
   * **Webhook Secret Key**: viene usata per verificare le firme HMAC dei webhook.

### Variabili d'ambiente

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

***

## Passaggio 2: Crea un workflow

Prima di creare le sessioni, ti serve un workflow di verifica. I workflow definiscono i passaggi di verifica che l'utente deve completare.

### Crea nella Console

1. Vai alla [Didit Console](https://business.didit.me) → **Workflows**
2. Fai clic su **Create Workflow**
3. Scegli un modello di base:

| Modello | Ideale per |
| - | - |
| **KYC** | Verifica dell'identità completa con documento d'identità |
| **Adaptive Age Verification** | Verifica dell'età con backup facoltativo tramite documento |
| **Biometric Authentication** | Nuova verifica degli utenti di ritorno |
| **Address Verification** | Verifica della prova di residenza |

4. Aggiungi le funzionalità facoltative:

| Funzionalità | Descrizione |
| - | - |
| Verifica NFC | Legge i dati del chip del passaporto o del documento d'identità |
| Rilevamento della vitalità | Previene lo spoofing con un video selfie |
| Confronto facciale | Confronta il selfie con la foto del documento |
| Verifica telefonica | Verifica il numero di telefono tramite SMS |
| Verifica email | Verifica l'indirizzo email |
| Screening AML | Controlla le liste di sanzioni e PEP |
| Validazione su database | Verifica sulle banche dati governative |
| Analisi dispositivo e IP | Rileva VPN, proxy e geolocalizzazione |

5. Copia l'**ID del workflow**

> [!NOTE]
>
> Vedi la [Documentazione sui workflow](https://docs.didit.me/it/console/workflows.md) per le opzioni di configurazione dettagliate.

***

## Passaggio 3: Crea una sessione di verifica

Chiama l'API per creare una sessione per il tuo utente.

### Richiesta

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

### Parametri della richiesta

| Parametro | Obbligatorio | Descrizione |
| - | - | - |
| `workflow_id` | Sì | L'UUID del tuo workflow, disponibile nella Console. |
| `callback` | No | URL a cui viene inviato l'utente dopo la verifica. Didit aggiunge `verificationSessionId` e `status` come parametri di query. Supporta schemi personalizzati (ad esempio `myapp://`). Se omesso, viene usato l'URL di callback configurato nel workflow. |
| `callback_method` | No | Quale dispositivo gestisce il reindirizzamento: `initiator`, `completer` o `both`. Predefinito: `initiator`. |
| `vendor_data` | No | Il tuo identificativo interno dell'utente, riportato nei webhook per la correlazione. |
| `metadata` | No | Oggetto JSON arbitrario memorizzato con la sessione e restituito nei webhook. |
| `language` | No | Suggerimento sulla lingua dell'interfaccia (ISO 639-1, ad esempio `en`, `es`). Se omesso, viene rilevata automaticamente la lingua del browser. |
| `contact_details` | No | Email e telefono dell'utente per la precompilazione e le notifiche email. |
| `expected_details` | No | Dati attesi dell'utente per la validazione incrociata con i dati verificati. |
| `portrait_image` | Condizionale | Immagine del volto codificata in Base64 (massimo 2 MB; JPEG, PNG, WebP o TIFF). Volto di riferimento per i workflow di Autenticazione biometrica o che iniziano con il Confronto facciale. Facoltativo quando l'utente identificato da `vendor_data` ha già un volto memorizzato (volto approvato dal Rilevamento della vitalità, foto del passaporto elettronico, foto del documento d'identità o volto del profilo registrato): Didit lo riutilizza automaticamente; `400` se non ce n'è nessuno disponibile. |
| `sandbox_scenario` | No | Slug di scenario valido solo in sandbox (ad esempio `approve`, `decline_aml_hit`) che popola automaticamente la sessione con input speciali. |

### Esempio completo con tutte le opzioni

```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` limita il passaggio di verifica
> dell'identità a tipi di documento specifici: la schermata di selezione del
> documento mostra solo i tipi richiesti. Valori ammessi: `P` (passaporto), `ID`
> (carta d'identità nazionale), `DL` (patente di guida), `RP` (permesso di
> soggiorno), `HIC` (tessera assicurativa sanitaria), `TC` (tessera fiscale) e
> `SSC` (tessera previdenziale). I valori non distinguono maiuscole e minuscole e
> i duplicati vengono eliminati; i valori sconosciuti restituiscono `400`. Consulta
> lo [schema completo nel riferimento Create Session](https://docs.didit.me/it/sessions-api/create-session.md).

### Risposta

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

### Campi principali della risposta

| Campo | Descrizione |
| - | - |
| `session_id` | UUID univoco di questa sessione. Usalo per `GET /v3/session/{sessionId}/decision/` e per i webhook. |
| `session_number` | Numero di sequenza leggibile, mostrato nella console Didit. |
| `session_token` | Token di 12 caratteri compatibile con gli URL che autorizza l'utente finale ad aprire il flusso ospitato e inizializza gli SDK nativi (l'SDK web usa invece `url`). Trattalo come un segreto. |
| `url` | URL della pagina di verifica ospitata a cui reindirizzare l'utente o da incorporare in un iframe. (Il nome del campo è `url`, non `verification_url`.) Quando la richiesta include `language`, l'URL contiene un segmento di percorso con la lingua, ad esempio `https://verify.didit.me/en/session/3FaJ9wLqX2Mz`. |
| `status` | Stato attuale: `"Not Started"` per una sessione appena creata. |
| `workflow_id` | Identificativo stabile del workflow su cui viene eseguita la sessione. |
| `workflow_version` | Versione pubblicata del workflow a cui la sessione era associata al momento della creazione. |

> [!NOTE]
>
> **Idempotenza.** Quando `vendor_data` è fornito e nell'ultima versione pubblicata
> del workflow esiste già una sessione non completata (`Not Started`, `In Progress`,
> `Resubmitted` o `Awaiting User`) con lo stesso `vendor_data`, viene restituita
> quella sessione (sempre con `201`) invece di crearne un duplicato. Il suo
> `callback` e i suoi `metadata` vengono aggiornati con i nuovi valori, e `status`
> riflette lo stato della sessione esistente.

> [!NOTE]
>
> **Se manca `x-api-key`, la risposta è `403`** con
> `{"detail": "You do not have permission to perform this action."}`, lo stesso
> corpo restituito per una chiave valida ma senza autorizzazioni. Una chiave
> errata, revocata o scaduta restituisce `401`. Crediti insufficienti restituiscono
> `400`. Consulta [Create Session API Reference](https://docs.didit.me/it/sessions-api/create-session.md) per la documentazione completa.

***

## Passaggio 4: Presenta la verifica all'utente

Scegli come presentare il flusso di verifica al tuo utente:

### Opzione A: SDK nativo (consigliato per il mobile)

Usa il `session_token` con gli SDK nativi per l'integrazione di fotocamera, NFC e biometria.

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

### Opzione B: iframe InContext (consigliato per il web)

Incorpora la verifica nella tua pagina usando l'`url` restituito nel Passaggio 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/it/integration/web-sdks/incontext-iframe.md)

### Opzione C: Reindirizzamento

Reindirizza l'utente all'URL di verifica.

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

→ [Reindirizzamento web](https://docs.didit.me/it/integration/web-sdks/web-redirect.md)

### Opzione D: WebView mobile

Per le app mobili senza SDK nativo.

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

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

***

## Step 5: Ricevere i risultati

### Tramite webhook (consigliato)

I webhook notificano il tuo server in tempo reale quando lo stato di una verifica cambia.

**Configurazione:**

1. Crea una destinazione webhook nella [Console](https://business.didit.me) (**API e Webhook**) oppure tramite [`POST /v3/webhook/destinations/`](https://docs.didit.me/it/management-api/webhook/create-destination) con `webhook_version: "v3"` e un array `subscribed_events` (ad esempio `["status.updated"]`)
2. Conserva il `secret_shared_key` restituito alla creazione: è il segreto HMAC per tutte le varianti della firma
3. Implementa un endpoint webhook sul tuo server

**Esempio di payload webhook:**

All'interno di `decision`, ogni risultato per singola funzionalità viene consegnato come **array plurale** (`id_verifications[]`, `nfc_verifications[]`, `liveness_checks[]`, `face_matches[]`, …), con una voce per ogni nodo del workflow. Questo è il contratto V3 ed è vincolante: non scrivere mai codice che usi le chiavi al singolare `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'oggetto `decision` è presente solo quando `status` è `Approved`, `Declined`, `In Review` o `Abandoned` (un evento `Resubmitted` contiene invece `resubmit_info`). Didit riutilizza lo stesso `event_id` nei nuovi tentativi e nelle destinazioni multiple: usa quell'ID per gestire l'idempotenza. Consulta [Modelli dei dati](https://docs.didit.me/it/reference/data-models.md) per tutti gli array plurali e i campi.

**Verifica della firma (X-Signature-V2, consigliato):**

`X-Signature-V2` è un HMAC-SHA256 calcolato su una ricodifica canonica del corpo JSON: i float con valore intero vengono accorciati a interi, le chiavi vengono ordinate ricorsivamente, i separatori sono compatti e i caratteri Unicode vengono preservati.

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

→ [Documentazione completa sui webhook](https://docs.didit.me/it/integration/webhooks.md), inclusi la `X-Signature` sui byte grezzi, la variante obsoleta `X-Signature-Simple` che copre solo l'envelope, e come scegliere tra le due. Didit ritenta le consegne fallite (`5xx`, `404`, timeout) fino a 2 volte (dopo circa 1 minuto, poi dopo circa 4 minuti) e va in timeout dopo **5 secondi**: restituisci `2xx` rapidamente.

### Tramite API (facoltativo)

Recupera in qualsiasi momento lo stesso payload della decisione:

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

La struttura della risposta (array plurali) è identica all'oggetto `decision` contenuto nel payload del webhook, e l'endpoint può essere chiamato in qualsiasi momento del ciclo di vita della sessione. Usa i webhook per gli aggiornamenti in tempo reale e riserva le chiamate API al recupero di dati pregressi, all'audit e alla ricostruzione: l'endpoint `/decision/` è limitato a 600 richieste GET al minuto per chiave API (vedi [Limitazione della frequenza](https://docs.didit.me/it/integration/rate-limiting.md)). Gestione degli errori: un `session_id` sconosciuto restituisce `404` con `{"detail": "Not found."}`; gli errori di autenticazione restituiscono `403`, mai `401`. Gli URL dei media nella risposta sono link con firma preautorizzata a breve scadenza: recuperali subito invece di conservarli.

→ [Riferimento API per il recupero della sessione](https://docs.didit.me/it/sessions-api/retrieve-session.md)

***

## Esempi di codice completi

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

***

## Stati della sessione

Sono i 10 possibili valori di `status`: stringhe esatte, sensibili alle maiuscole (nota che `Kyc Expired` ha una sola `K` maiuscola).

| Stato | Descrizione |
| - | - |
| `Not Started` | Sessione creata, ma l'utente non ha ancora aperto il link. |
| `In Progress` | L'utente sta completando la verifica. |
| `In Review` | Verifica segnalata per una revisione manuale. |
| `Approved` | Tutti i controlli superati. |
| `Declined` | Uno o più controlli non superati. |
| `Resubmitted` | Il revisore ha richiesto di ripetere passaggi specifici. |
| `Awaiting User` | Sessione padre KYB in attesa che le parti KYC figlie completino la verifica. |
| `Abandoned` | L'utente ha iniziato, ma non ha completato la verifica nel tempo previsto. |
| `Expired` | La sessione è scaduta prima che l'utente aprisse il link. |
| `Kyc Expired` | Sessione precedentemente approvata il cui documento ha superato la data di scadenza. |

Consulta [Stati di verifica](https://docs.didit.me/it/integration/verification-statuses.md) per il ciclo di vita completo, le transizioni e il diagramma Mermaid.

***

## Prossimi passi

1. **Scegli il metodo di integrazione**:
   * App mobili → [SDK nativi](https://docs.didit.me/it/integration/native-sdks/overview.md)
   * App web → [SDK web](https://docs.didit.me/it/integration/web-sdks/overview.md)
2. **Configura i webhook**: [Webhook](https://docs.didit.me/it/integration/webhooks.md)
3. **Pianifica la capacità**: [Limitazione della frequenza](https://docs.didit.me/it/integration/rate-limiting.md)
4. **Gestisci ogni stato**: [Stati di verifica](https://docs.didit.me/it/integration/verification-statuses.md)
5. **Personalizza il tuo workflow**: [Workflow](https://docs.didit.me/it/console/workflows.md)
6. **Verifica che l'API sia raggiungibile**: [Healthcheck](https://docs.didit.me/it/api-reference/healthcheck.md)

## Hai bisogno di aiuto?

* **Supporto**: [support@didit.me](mailto:support@didit.me)
* **Onboarding programmatico**: [Registrazione programmatica](https://docs.didit.me/it/integration/programmatic-registration.md)


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