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

# Fluxo completo da API: integração KYC ponta a ponta

> Integre a API REST da Didit de ponta a ponta: obtenha credenciais, crie uma sessão, partilhe a ligação, trate webhooks e obtenha decisões KYC em minutos.

Este guia orienta-o pelo fluxo completo de integração da verificação de identidade da Didit através da API REST: obter as credenciais, criar uma sessão, apresentá-la ao utilizador e receber os resultados.

***

## Passo 1: Obter as suas credenciais

Tem duas formas de obter um `api_key`:

### Opção A: Registo programático (sem navegador)

Ideal para CI/CD e agentes de IA. Duas chamadas à API — `POST /programmatic/register/` e depois `POST /programmatic/verify-email/` — devolvem o `api_key` na própria resposta.

→ Consulte o [Registo programático](https://docs.didit.me/pt/integration/programmatic-registration.md) para ver o fluxo completo, as regras de palavra-passe, a validade do JWT e a política de bloqueio.

### Opção B: Através da Didit Console

1. Aceda à [Didit Console](https://business.didit.me).
2. Crie ou selecione a sua organização.
3. Navegue até **Definições** → **API e Webhooks**.
4. Copie:
   * **Chave de API** — usada como cabeçalho `x-api-key` em cada chamada de verificação.
   * **Chave secreta do webhook** — usada para verificar as assinaturas HMAC dos webhooks.

### Variáveis de 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
```

***

## Passo 2: Criar um fluxo de verificação

Antes de criar sessões, precisa de um fluxo de verificação. Os fluxos definem os passos de verificação pelos quais os utilizadores passam.

### Criar na consola

1. Aceda à [Didit Console](https://business.didit.me) → **Fluxos**
2. Clique em **Criar fluxo**
3. Escolha um modelo base:

| Modelo | Ideal para |
| - | - |
| **KYC** | Verificação de identidade completa com documento de identificação |
| **Verificação de idade adaptativa** | Verificação de idade com recurso opcional a documento de identificação |
| **Autenticação biométrica** | Reverificação de utilizadores que regressam |
| **Verificação de endereço** | Verificação de comprovativo de morada |

4. Adicione funcionalidades opcionais:

| Funcionalidade | Descrição |
| - | - |
| Verificação NFC | Ler os dados do chip do passaporte/documento de identificação |
| Prova de vida | Impedir spoofing com vídeo de selfie |
| Correspondência facial | Comparar a selfie com a fotografia do documento |
| Verificação de telefone | Validar o número de telefone por SMS |
| Verificação de e-mail | Validar o endereço de e-mail |
| Triagem AML | Verificar contra listas de sanções/PEP |
| Validação de base de dados | Validar contra bases de dados governamentais |
| Análise de dispositivo e IP | Detetar VPN, proxies e geolocalização |

5. Copie o seu **ID do fluxo**

> [!NOTE]
>
> Consulte a [documentação dos fluxos](https://docs.didit.me/pt/console/workflows.md) para ver as opções de configuração detalhadas.

***

## Passo 3: Criar uma sessão de verificação

Chame a API para criar uma sessão para o seu utilizador.

### Pedido

```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âmetros do pedido

| Parâmetro | Obrigatório | Descrição |
| - | - | - |
| `workflow_id` | Sim | O UUID do seu fluxo, disponível na Consola. |
| `callback` | Não | URL para onde o utilizador é enviado após a verificação. A Didit acrescenta `verificationSessionId` e `status` como parâmetros de consulta. Suporta esquemas personalizados (por exemplo, `myapp://`). Se for omitido, é utilizado o URL de retorno configurado no fluxo. |
| `callback_method` | Não | Dispositivo que processa o redirecionamento: `initiator`, `completer` ou `both`. Predefinição: `initiator`. |
| `vendor_data` | Não | O identificador interno do seu utilizador, devolvido nos webhooks para correlação. |
| `metadata` | Não | Objeto JSON arbitrário guardado com a sessão e devolvido nos webhooks. |
| `language` | Não | Sugestão de idioma da interface (ISO 639-1, por exemplo, `en`, `es`). Se for omitido, o idioma do navegador é detetado automaticamente. |
| `contact_details` | Não | E-mail ou telefone do utilizador para pré-preenchimento e notificações por e-mail. |
| `expected_details` | Não | Dados esperados do utilizador para validação cruzada com os dados verificados. |
| `portrait_image` | Condicional | Imagem facial codificada em Base64 (máximo de 2 MB; JPEG, PNG, WebP ou TIFF). Rosto de referência para fluxos de Autenticação biométrica ou fluxos que priorizam a Correspondência facial. É opcional quando o utilizador identificado por `vendor_data` já tem um rosto guardado (rosto de prova de vida aprovado, fotografia de passaporte eletrónico, retrato do documento de identidade ou rosto de perfil registado): a Didit reutiliza-o automaticamente; devolve `400` se nenhum estiver disponível. |
| `sandbox_scenario` | Não | Identificador de cenário exclusivo do sandbox (por exemplo, `approve`, `decline_aml_hit`) que preenche automaticamente a sessão com valores mágicos. |

### Exemplo completo com todas as opções

```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` restringe a etapa de verificação de identidade
> a tipos de documento específicos: o ecrã de seleção de documentos mostra apenas os tipos
> pedidos. Valores permitidos: `P` (passaporte), `ID` (documento de identidade nacional), `DL`
> (carta de condução), `RP` (título de residência), `HIC` (cartão de seguro de saúde),
> `TC` (cartão fiscal), `SSC` (cartão de segurança social). Os valores não diferenciam
> maiúsculas de minúsculas e são eliminados os duplicados; valores desconhecidos devolvem `400`. Consulte a
> [referência de criação de sessão](https://docs.didit.me/pt/sessions-api/create-session.md) para o esquema completo.

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

### Campos principais da resposta

| Campo | Descrição |
| - | - |
| `session_id` | UUID único desta sessão. Use-o em `GET /v3/session/{sessionId}/decision/` e nos webhooks. |
| `session_number` | Número de sequência legível apresentado na consola Didit. |
| `session_token` | Token de 12 caracteres seguro para URL que autoriza o utilizador final a abrir o fluxo alojado e inicializa os SDK nativos (o SDK web utiliza `url`). Trate-o como um segredo. |
| `url` | URL de verificação alojada para onde redirecionar o utilizador ou para incorporar num iframe. (O nome do campo é `url`, não `verification_url`.) Quando o pedido inclui `language`, o URL contém um segmento de idioma no caminho, por exemplo `https://verify.didit.me/en/session/3FaJ9wLqX2Mz`. |
| `status` | Estado atual: `"Not Started"` para uma sessão recém-criada. |
| `workflow_id` | Identificador estável do fluxo em que a sessão é executada. |
| `workflow_version` | Versão publicada do fluxo à qual a sessão ficou associada na criação. |

> [!NOTE]
>
> **Idempotência.** Quando é fornecido `vendor_data` e já existe uma sessão não concluída
> (`Not Started`, `In Progress`, `Resubmitted` ou `Awaiting User`) com o mesmo `vendor_data`
> na versão publicada mais recente do fluxo, essa sessão existente é devolvida (ainda com `201`) em vez de criar
> um duplicado. O seu `callback` e `metadata` são atualizados para os novos valores, e
> `status` reflete o estado da sessão existente.

> [!NOTE]
>
> **Um `x-api-key` em falta devolve `403`**, com
> `{"detail": "You do not have permission to perform this action."}`, o mesmo corpo
> de uma chave válida sem permissão. Uma chave incorreta, revogada ou expirada devolve `401`. Créditos insuficientes devolvem
> `400`. Consulte a [referência da API de Criar sessão](https://docs.didit.me/pt/sessions-api/create-session.md) para a
> documentação completa.

***

## Passo 4: Apresentar a verificação ao utilizador

Escolha a forma como pretende apresentar o fluxo de verificação ao seu utilizador:

### Opção A: SDK nativo (recomendado para dispositivos móveis)

Utilize o `session_token` com os SDK nativos para a câmara, NFC e integração biométrica.

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

### Opção B: iframe InContext (recomendado para a web)

Incorpore a verificação na sua página com o `url` devolvido no Passo 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/pt/integration/web-sdks/incontext-iframe.md)

### Opção C: Redirecionamento

Redirecione o utilizador para o URL de verificação.

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

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

### Opção D: WebView móvel

Para aplicações móveis sem SDK nativo.

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

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

***

## Passo 5: Receber resultados

### Através de webhook (recomendado)

Os webhooks notificam o seu servidor em tempo real sempre que o estado da verificação muda.

**Configuração:**

1. Crie um destino do webhook na [Consola](https://business.didit.me) (**API e Webhooks**) ou através de [`POST /v3/webhook/destinations/`](https://docs.didit.me/pt/management-api/webhook/create-destination) com `webhook_version: "v3"` e um array `subscribed_events` (por exemplo, `["status.updated"]`)
2. Armazene o `secret_shared_key` devolvido na criação, que é o segredo partilhado HMAC para todas as variantes de assinatura
3. Implemente um endpoint de webhook no seu servidor

**Exemplo de payload de webhook:**

Dentro de `decision`, cada resultado por funcionalidade é entregue como um **array plural** (`id_verifications[]`, `nfc_verifications[]`, `liveness_checks[]`, `face_matches[]`, …), com uma entrada por nó do fluxo. Este é o contrato V3 e é intocável: nunca use chaves no singular `nfc`, `id_verification` ou `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": []
  }
}
```

O objeto `decision` só está presente quando `status` é `Approved`, `Declined`, `In Review` ou `Abandoned` (um evento `Resubmitted` inclui `resubmit_info`). A Didit reutiliza o mesmo `event_id` nas novas tentativas e nos destinos de distribuição, por isso use-o como chave de idempotência. Consulte [Modelos de dados](https://docs.didit.me/pt/reference/data-models.md) para ver todos os arrays plurais e campos.

**Verificar a assinatura (X-Signature-V2, recomendado):**

`X-Signature-V2` é um HMAC-SHA256 sobre uma recodificação canónica do corpo JSON: valores float com valor inteiro são encurtados para inteiros, as chaves são ordenadas recursivamente, usam-se separadores compactos e o Unicode é preservado.

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

→ [Documentação completa sobre webhooks](https://docs.didit.me/pt/integration/webhooks.md), incluindo o `X-Signature` com bytes brutos, o `X-Signature-Simple` obsoleto, que só cobre o envelope, e como escolher. A Didit repete as entregas falhadas (`5xx`, `404`, tempo limite) até 2 vezes (cerca de 1 min, depois cerca de 4 min) e o tempo limite é de **5 segundos**: devolva `2xx` rapidamente.

### Através da API (opcional)

Obtenha o mesmo payload de decisão em qualquer momento:

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

A estrutura da resposta (arrays plurais) é idêntica ao objeto `decision` do payload do webhook, e o endpoint pode ser chamado em qualquer momento do ciclo de vida da sessão. Use os webhooks para atualizações em tempo real e reserve as chamadas à API para sincronização retroativa, auditoria e reconstruções. O endpoint `/decision/` tem limite de 600 pedidos GET por minuto por chave de API (consulte [Limites de pedidos](https://docs.didit.me/pt/integration/rate-limiting.md)). Semântica de erros: um `session_id` desconhecido devolve `404` com `{"detail": "Not found."}`; as falhas de autenticação aparecem como `403`, e este endpoint nunca devolve `401`. Os URLs de multimédia na resposta são ligações pré-assinadas de curta duração, por isso obtenha-os de imediato em vez de os guardar.

→ [Consultar a referência da API de sessão](https://docs.didit.me/pt/sessions-api/retrieve-session.md)

***

## Exemplos de código completos

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

***

## Estados da sessão

Estes são os 10 valores possíveis de `status`, strings exatas e sensíveis a maiúsculas e minúsculas (repare que `Kyc Expired` usa um único `K` maiúsculo):

| Estado | Descrição |
| - | - |
| `Not Started` | Sessão criada, mas o utilizador ainda não abriu a ligação. |
| `In Progress` | O utilizador está a concluir a verificação. |
| `In Review` | Verificação sinalizada para revisão manual. |
| `Approved` | Todas as verificações foram aprovadas. |
| `Declined` | Uma ou mais verificações falharam. |
| `Resubmitted` | O revisor pediu que determinados passos fossem refeitos. |
| `Awaiting User` | Sessão KYB principal a aguardar a conclusão das sessões KYC das partes secundárias. |
| `Abandoned` | O utilizador iniciou, mas não concluiu no tempo previsto. |
| `Expired` | A sessão expirou antes de o utilizador abrir a ligação. |
| `Kyc Expired` | Sessão anteriormente aprovada cujo documento ultrapassou a data de validade. |

Consulte [Estados de verificação](https://docs.didit.me/pt/integration/verification-statuses.md) para o ciclo de vida completo, as transições e o diagrama Mermaid.

***

## Próximos passos

1. **Escolha o seu método de integração**:
   * Aplicações móveis → [SDKs nativos](https://docs.didit.me/pt/integration/native-sdks/overview.md)
   * Aplicações web → [SDKs web](https://docs.didit.me/pt/integration/web-sdks/overview.md)
2. **Configure os webhooks**: [Webhooks](https://docs.didit.me/pt/integration/webhooks.md)
3. **Planeie a capacidade**: [Limites de pedidos](https://docs.didit.me/pt/integration/rate-limiting.md)
4. **Trate todos os estados**: [Estados de verificação](https://docs.didit.me/pt/integration/verification-statuses.md)
5. **Personalize o seu fluxo**: [Fluxos](https://docs.didit.me/pt/console/workflows.md)
6. **Verifique se a API está acessível**: [Verificação do estado](https://docs.didit.me/pt/api-reference/healthcheck.md)

## Precisa de ajuda?

* **Suporte**: [support@didit.me](mailto:support@didit.me)
* **Onboarding programático**: [Registo programático](https://docs.didit.me/pt/integration/programmatic-registration.md)


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