# Contrato.Cloud KYC

Revision: ab4c5635b7f0f446ba6cfcf3607f9fb94636e233074478aed2ea1a9c23f83ead

# API KYC: da identificação ao resultado

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/visao-geral

## Um fluxo, três responsabilidades

Seu servidor cria uma transação pela API. A pessoa realiza a verificação na jornada hospedada, acessada pelo link retornado. Seu sistema consulta o resultado e recebe eventos para continuar o processo de negócio.

1. **Criar:** use `POST /v1/face-auth/transactions` com `X-API-Key` no seu backend.
2. **Encaminhar:** utilize a URL retornada; a chave nunca vai para o navegador da pessoa.
3. **Acompanhar:** associe `transaction_id` ao seu processo, valide webhooks e reconcilie o estado por GET.

## O que o resultado representa

A verificação de identidade é uma etapa do seu fluxo. Aprovar uma identidade não aprova automaticamente crédito, uma compra, uma conta ou poderes de representação. Seu sistema continua responsável pelas regras comerciais, pelo tratamento de pendências e pela decisão de continuidade.

`status` descreve a etapa operacional; `decision` informa `pending`, `approved` ou `rejected`. Não transforme uma etapa intermediária em aprovação. Uma jornada expirada também exige tratamento específico.

## Escolha como começar

Para compreender o fluxo sem credenciais ou dados pessoais, use o [simulador local](/pt-BR/desenvolvedores/kyc/simulador). Para integrar seu backend, siga o [início rápido](/pt-BR/desenvolvedores/kyc/inicio-rapido) e consulte a [referência](/pt-BR/desenvolvedores/kyc/referencia).

Os exemplos usam dados sintéticos e um endereço reservado para documentação. A URL de API, as credenciais e os acessos do seu ambiente são definidos na habilitação da integração.


---

# Integre sua primeira jornada de KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/inicio-rapido

## Antes de executar

Os comandos são exemplos sintéticos. `api.example.invalid` não é um ambiente de testes e não resolve como serviço real. Substitua a base somente pelo endereço confirmado para seu ambiente habilitado e mantenha a chave no backend. Para experimentar sem rede ou credenciais, abra o [simulador](/pt-BR/desenvolvedores/kyc/simulador).

## 1. Crie uma transação

Escolha uma chave de idempotência estável para a mesma tentativa de criação. O CPF abaixo é um valor fictício de documentação.

Criar transação KYC

### cURL

```bash
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
set -eu
: "${KYC_API_BASE_URL:?Set authorized base}" "${KYC_API_KEY:?Set API key}"
: "${KYC_IDEMPOTENCY_KEY:?Persist the key for this creation intent}"
curl --fail-with-body --max-time 15 --request POST \
  "${KYC_API_BASE_URL%/}/v1/face-auth/transactions" \
  --header "X-API-Key: ${KYC_API_KEY}" \
  --header "Idempotency-Key: ${KYC_IDEMPOTENCY_KEY}" \
  --header 'Content-Type: application/json' \
  --data-binary '{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}'
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### JavaScript / Node.js

```javascript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name) {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions", {
  method: "POST",
  headers: { "X-API-Key": required("KYC_API_KEY"), "Content-Type": "application/json", "Idempotency-Key": required("KYC_IDEMPOTENCY_KEY") },
  body: "{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}",
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 201) throw new Error("KYC HTTP " + response.status);
const transaction = await response.json();
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### TypeScript / Node.js

```typescript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions", {
  method: "POST",
  headers: { "X-API-Key": required("KYC_API_KEY"), "Content-Type": "application/json", "Idempotency-Key": required("KYC_IDEMPOTENCY_KEY") },
  body: "{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}",
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 201) throw new Error("KYC HTTP " + response.status);
const transaction: unknown = await response.json();
// Validate the response against the curated OpenAPI before using it.
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Python

```python
import json
import os
from urllib.parse import quote
from urllib.request import Request, urlopen
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
base = os.environ["KYC_API_BASE_URL"].rstrip("/")
request = Request(base + "/v1/face-auth/transactions",
    method="POST",
    headers={"X-API-Key": os.environ["KYC_API_KEY"], "Content-Type": "application/json",
             "Idempotency-Key": os.environ["KYC_IDEMPOTENCY_KEY"]},
    data="{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}".encode("utf-8"),
)
with urlopen(request, timeout=15) as response:
    if response.status != 201:
        raise RuntimeError("KYC HTTP " + str(response.status))
    transaction = json.load(response)
    print("HTTP " + str(response.status))
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### PHP

```php
<?php
// PHP 8+, standard HTTP streams. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(string $name): string {
    $value = getenv($name);
    if ($value === false || $value === '') throw new RuntimeException("Missing configuration: " . $name);
    return $value;
}
$url = rtrim(required('KYC_API_BASE_URL'), '/') . '/v1/face-auth/transactions';
$headers = ['X-API-Key: ' . required('KYC_API_KEY'), 'Content-Type: application/json', 'Idempotency-Key: ' . required('KYC_IDEMPOTENCY_KEY')];
$context = stream_context_create(['http' => ['method' => 'POST', 'header' => implode("\r\n", $headers),
    'timeout' => 15, 'ignore_errors' => true, 'content' => '{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}']]);
$body = file_get_contents($url, false, $context);
if ($body === false) throw new RuntimeException('KYC transport failure');
if (!preg_match('/^HTTP\/\S+ ([0-9]{3})/', $http_response_header[0] ?? '', $matches)) throw new RuntimeException('Invalid HTTP response');
$status = (int) $matches[1];
if ($status !== 201) throw new RuntimeException('KYC HTTP ' . $status);
$transaction = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo 'HTTP ' . $status . PHP_EOL;
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### C# / .NET

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
// .NET 8+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
static string Required(string name) => Environment.GetEnvironmentVariable(name) is { Length: > 0 } value ? value : throw new Exception("Missing configuration: " + name);
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
using var request = new HttpRequestMessage(HttpMethod.Post, Required("KYC_API_BASE_URL").TrimEnd('/') + "/v1/face-auth/transactions");
request.Headers.Add("X-API-Key", Required("KYC_API_KEY"));
request.Headers.Add("Idempotency-Key", Required("KYC_IDEMPOTENCY_KEY"));
request.Content = new StringContent("{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
if ((int)response.StatusCode != 201) throw new Exception("KYC HTTP " + (int)response.StatusCode);
using var transaction = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine("HTTP " + (int)response.StatusCode);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Java

```java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
// Java 17+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
class Example {
  static String required(String name) {
    String value = System.getenv(name);
    if (value == null || value.isEmpty()) throw new IllegalStateException("Missing configuration: " + name);
    return value;
  }
  public static void main(String[] args) throws Exception {
    String base = required("KYC_API_BASE_URL").replaceAll("/+$", "");
    String url = base + "/v1/face-auth/transactions";
    var builder = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(15))
        .header("X-API-Key", required("KYC_API_KEY"));
    builder.header("Idempotency-Key", required("KYC_IDEMPOTENCY_KEY"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString("{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}", StandardCharsets.UTF_8));
    var response = HttpClient.newHttpClient().send(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
    if (response.statusCode() != 201) throw new IllegalStateException("KYC HTTP " + response.statusCode());
    System.out.println("HTTP " + response.statusCode());
    // Parse response.body() with your application's JSON parser and validate against OpenAPI.
    // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
  }
}
```

### Go

```go
package main
import (
  "encoding/json"
  "fmt"
  "net/http"
  "net/url"
  "os"
  "strings"
  "time"
)
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
func required(name string) string {
  value := os.Getenv(name); if value == "" { panic("Missing configuration: " + name) }; return value
}
func main() {
  _ = url.PathEscape // Used by the GET variant.
  endpoint := strings.TrimRight(required("KYC_API_BASE_URL"), "/") + "/v1/face-auth/transactions"
  request, err := http.NewRequest("POST", endpoint, strings.NewReader(`{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}`)); if err != nil { panic(err) }
  request.Header.Set("X-API-Key", required("KYC_API_KEY"))
  request.Header.Set("Content-Type", "application/json")
  request.Header.Set("Idempotency-Key", required("KYC_IDEMPOTENCY_KEY"))
  response, err := (&http.Client{Timeout: 15*time.Second}).Do(request); if err != nil { panic(err) }; defer response.Body.Close()
  if response.StatusCode != 201 { panic(fmt.Sprintf("KYC HTTP %d", response.StatusCode)) }
  var transaction map[string]any
  if err := json.NewDecoder(response.Body).Decode(&transaction); err != nil { panic(err) }
  fmt.Printf("HTTP %d\n", response.StatusCode)
  // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
}
```

## 2. Encaminhe a jornada

O POST retorna `201` e o identificador `transaction_id`. Guarde esse identificador junto ao seu `external_id`. Encaminhe a pessoa usando a URL hospedada retornada em `links.webview_url`; não construa links a partir do identificador da transação.

## 3. Consulte o resultado

Consultar e reconciliar transação KYC

### cURL

```bash
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
set -eu
: "${KYC_API_BASE_URL:?Set authorized base}" "${KYC_API_KEY:?Set API key}"
: "${KYC_TRANSACTION_ID:?Use the returned transaction_id}"
curl --fail-with-body --max-time 15 --request GET \
  "${KYC_API_BASE_URL%/}/v1/face-auth/transactions/${KYC_TRANSACTION_ID}" \
  --header "X-API-Key: ${KYC_API_KEY}"
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### JavaScript / Node.js

```javascript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name) {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions" + "/" + encodeURIComponent(required("KYC_TRANSACTION_ID")), {
  method: "GET",
  headers: { "X-API-Key": required("KYC_API_KEY") },
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 200) throw new Error("KYC HTTP " + response.status);
const transaction = await response.json();
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### TypeScript / Node.js

```typescript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions" + "/" + encodeURIComponent(required("KYC_TRANSACTION_ID")), {
  method: "GET",
  headers: { "X-API-Key": required("KYC_API_KEY") },
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 200) throw new Error("KYC HTTP " + response.status);
const transaction: unknown = await response.json();
// Validate the response against the curated OpenAPI before using it.
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Python

```python
import json
import os
from urllib.parse import quote
from urllib.request import Request, urlopen
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
base = os.environ["KYC_API_BASE_URL"].rstrip("/")
request = Request(base + "/v1/face-auth/transactions" + "/" + quote(os.environ["KYC_TRANSACTION_ID"], safe=""),
    method="GET",
    headers={"X-API-Key": os.environ["KYC_API_KEY"]},
)
with urlopen(request, timeout=15) as response:
    if response.status != 200:
        raise RuntimeError("KYC HTTP " + str(response.status))
    transaction = json.load(response)
    print("HTTP " + str(response.status))
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### PHP

```php
<?php
// PHP 8+, standard HTTP streams. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(string $name): string {
    $value = getenv($name);
    if ($value === false || $value === '') throw new RuntimeException("Missing configuration: " . $name);
    return $value;
}
$url = rtrim(required('KYC_API_BASE_URL'), '/') . '/v1/face-auth/transactions' . '/' . rawurlencode(required('KYC_TRANSACTION_ID'));
$headers = ['X-API-Key: ' . required('KYC_API_KEY')];
$context = stream_context_create(['http' => ['method' => 'GET', 'header' => implode("\r\n", $headers),
    'timeout' => 15, 'ignore_errors' => true]]);
$body = file_get_contents($url, false, $context);
if ($body === false) throw new RuntimeException('KYC transport failure');
if (!preg_match('/^HTTP\/\S+ ([0-9]{3})/', $http_response_header[0] ?? '', $matches)) throw new RuntimeException('Invalid HTTP response');
$status = (int) $matches[1];
if ($status !== 200) throw new RuntimeException('KYC HTTP ' . $status);
$transaction = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo 'HTTP ' . $status . PHP_EOL;
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### C# / .NET

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
// .NET 8+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
static string Required(string name) => Environment.GetEnvironmentVariable(name) is { Length: > 0 } value ? value : throw new Exception("Missing configuration: " + name);
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
using var request = new HttpRequestMessage(HttpMethod.Get, Required("KYC_API_BASE_URL").TrimEnd('/') + "/v1/face-auth/transactions" + "/" + Uri.EscapeDataString(Required("KYC_TRANSACTION_ID")));
request.Headers.Add("X-API-Key", Required("KYC_API_KEY"));
using var response = await client.SendAsync(request);
if ((int)response.StatusCode != 200) throw new Exception("KYC HTTP " + (int)response.StatusCode);
using var transaction = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine("HTTP " + (int)response.StatusCode);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Java

```java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
// Java 17+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
class Example {
  static String required(String name) {
    String value = System.getenv(name);
    if (value == null || value.isEmpty()) throw new IllegalStateException("Missing configuration: " + name);
    return value;
  }
  public static void main(String[] args) throws Exception {
    String base = required("KYC_API_BASE_URL").replaceAll("/+$", "");
    String url = base + "/v1/face-auth/transactions" + "/" + URLEncoder.encode(required("KYC_TRANSACTION_ID"), StandardCharsets.UTF_8).replace("+", "%20");
    var builder = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(15))
        .header("X-API-Key", required("KYC_API_KEY"));
    builder.GET();
    var response = HttpClient.newHttpClient().send(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
    if (response.statusCode() != 200) throw new IllegalStateException("KYC HTTP " + response.statusCode());
    System.out.println("HTTP " + response.statusCode());
    // Parse response.body() with your application's JSON parser and validate against OpenAPI.
    // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
  }
}
```

### Go

```go
package main
import (
  "encoding/json"
  "fmt"
  "net/http"
  "net/url"
  "os"
  "strings"
  "time"
)
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
func required(name string) string {
  value := os.Getenv(name); if value == "" { panic("Missing configuration: " + name) }; return value
}
func main() {
  _ = url.PathEscape // Used by the GET variant.
  endpoint := strings.TrimRight(required("KYC_API_BASE_URL"), "/") + "/v1/face-auth/transactions" + "/" + url.PathEscape(required("KYC_TRANSACTION_ID"))
  request, err := http.NewRequest("GET", endpoint, nil); if err != nil { panic(err) }
  request.Header.Set("X-API-Key", required("KYC_API_KEY"))
  response, err := (&http.Client{Timeout: 15*time.Second}).Do(request); if err != nil { panic(err) }; defer response.Body.Close()
  if response.StatusCode != 200 { panic(fmt.Sprintf("KYC HTTP %d", response.StatusCode)) }
  var transaction map[string]any
  if err := json.NewDecoder(response.Body).Decode(&transaction); err != nil { panic(err) }
  fmt.Printf("HTTP %d\n", response.StatusCode)
  // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
}
```

Leia `status`, `decision` e `decision_reason` em conjunto. Uma decisão `pending` ainda não permite concluir seu fluxo como aprovado. A consulta utiliza a chave do mesmo Tenant e ambiente da transação.

## Próximo passo

Implemente [webhooks](/pt-BR/desenvolvedores/kyc/webhooks-e-eventos) com [validação HMAC](/pt-BR/desenvolvedores/kyc/validar-assinatura-hmac) e [reconciliação](/pt-BR/desenvolvedores/kyc/reentrega-e-reconciliacao). Antes de produção, cubra rejeição, expiração, revisão manual, duplicidades e perda temporária de conexão.


---

# Integre KYC com seu agente de IA

Source: https://contrato.cloud/pt-BR/desenvolvedores/integrar-com-ia

## Prepare a integração KYC com seu agente

Escolha a linguagem e copie as instruções abaixo para o seu agente de desenvolvimento. O contexto reúne os guias públicos, exemplos completos e a referência do contrato. Use dados sintéticos durante a preparação; a base de exemplo `.invalid` não é um ambiente de execução.

## Prompt e contexto da integração

### cURL

```text
Implemente uma integração backend de KYC da Contrato.Cloud em cURL, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=curl)
MCP tools: search_docs, read_doc, get_example

```

### JavaScript / Node.js

```text
Implemente uma integração backend de KYC da Contrato.Cloud em JavaScript / Node.js, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=javascript)
MCP tools: search_docs, read_doc, get_example

```

### TypeScript / Node.js

```text
Implemente uma integração backend de KYC da Contrato.Cloud em TypeScript / Node.js, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=typescript)
MCP tools: search_docs, read_doc, get_example

```

### Python

```text
Implemente uma integração backend de KYC da Contrato.Cloud em Python, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=python)
MCP tools: search_docs, read_doc, get_example

```

### PHP

```text
Implemente uma integração backend de KYC da Contrato.Cloud em PHP, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=php)
MCP tools: search_docs, read_doc, get_example

```

### C# / .NET

```text
Implemente uma integração backend de KYC da Contrato.Cloud em C# / .NET, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=csharp)
MCP tools: search_docs, read_doc, get_example

```

### Java

```text
Implemente uma integração backend de KYC da Contrato.Cloud em Java, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=java)
MCP tools: search_docs, read_doc, get_example

```

### Go

```text
Implemente uma integração backend de KYC da Contrato.Cloud em Go, usando somente a documentação pública indicada abaixo.
1. Leia o contexto e o OpenAPI antes de escrever código. Cite as páginas utilizadas; se um campo ou comportamento não estiver documentado, peça esclarecimento. Não invente API de assinaturas SIGN, rotas Admin ou Portal privado.
2. Use https://api.example.invalid como base sintética. Antes de qualquer execução real, solicite a base autorizada do ambiente e carregue a X-API-Key somente de variáveis de ambiente no backend. Nunca coloque chaves no frontend, prompt, URL, logs ou repositório.
3. Crie transações com POST /v1/face-auth/transactions e Idempotency-Key estável por operação lógica. Persista transaction_id e external_id. Encaminhe a pessoa pela links.webview_url retornada; não construa a URL da jornada.
4. Consulte GET /v1/face-auth/transactions/{transaction_id}. Trate status, decision e decision_reason em conjunto. pending e etapas intermediárias continuam pendentes; manual_review_pending aguarda revisão; approved exige decision=approved; rejected exige tratamento da recusa; expired encerra ou reinicia conforme política. Expiração não implica decision=rejected.
5. No receptor de webhooks, preserve os bytes do corpo bruto. Valide X-Face-Auth-Timestamp e X-Face-Auth-Signature (sha256=<hex>) com HMAC SHA-256 de timestamp + '.' + raw_body, comparação em tempo constante e janela temporal. Só depois interprete JSON e valide o envelope.
6. Deduplicate event_id em armazenamento durável, confira o identificador do header com o corpo e torne efeitos de negócio idempotentes. Reconcilie eventos fora de ordem e entregas perdidas consultando a transação atual; não rebaixe uma decisão nova por um evento antigo.
7. Entregue configuração, tratamento de erros, testes com fixtures sintéticas e instruções de execução. Cubra aprovação, rejeição, pending, expiração, revisão manual, duplicidade, corpo alterado, Unicode e timestamp vencido. Não execute chamadas reais nem processe dados pessoais sem autorização explícita.
8. cURL exemplifica somente HTTP. Se cURL for a preferência, use uma linguagem de backend suportada para o receptor/HMAC e explique a escolha. O MCP abaixo só consulta documentação pública: não cria transações, não assina e não cobra.

Context: https://contrato.cloud/downloads/ai/pt-BR/context.md
OpenAPI: https://contrato.cloud/downloads/kyc/openapi.json
Index: https://contrato.cloud/llms.txt
MCP (Streamable HTTP): https://contrato.cloud/api/mcp
MCP prompt: integrate_kyc (locale=pt-BR, language=go)
MCP tools: search_docs, read_doc, get_example

```

## Valide antes de executar

Revise o código produzido, configure a base autorizada no seu backend e teste idempotência, HMAC, reconciliação, expiração e revisão manual. Consulte o [checklist de produção](/pt-BR/desenvolvedores/kyc/entrada-em-producao). A documentação de IA e o MCP não recebem chaves KYC nem dados dos seus clientes.


---

# Autenticação e proteção das chaves de API

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/autenticacao

## Autenticação entre servidores

A API B2B usa o header `X-API-Key`. A chave determina o Tenant e o ambiente; não envie um identificador de outra empresa para tentar trocar esse escopo. As credenciais da API não são o login do Portal nem tokens do navegador.

```http
X-API-Key: REPLACE_ON_YOUR_SERVER
```

## Proteja o segredo

- Armazene a chave em um gerenciador de segredos ou configuração protegida do servidor.
- Nunca a inclua em JavaScript público, URLs, aplicativos distribuídos, repositórios ou chamados de suporte.
- Registre somente dados operacionais necessários. Mascare headers e dados pessoais nos logs.
- Separe credenciais e configuração por ambiente. Use apenas as permissões necessárias à integração.

Se suspeitar de exposição, interrompa o uso da chave comprometida, coordene revogação e substituição com o responsável pelo ambiente e confirme que os consumidores passaram a utilizar a nova credencial. A rotação não deve depender de alterar o aplicativo da pessoa verificada.

## Diagnóstico de acesso

`401` pode indicar chave ausente, inválida, revogada ou incompatível com o ambiente. `403` também pode representar ambiente desabilitado, permissão insuficiente ou IP não autorizado, conforme a operação. Não faça repetição infinita nesses casos.

Os downloads têm autorização própria; possuir uma transação não libera automaticamente seus arquivos. Veja [documentos e evidências](/pt-BR/desenvolvedores/kyc/documentos-e-evidencias) e consulte os códigos na [referência](/pt-BR/desenvolvedores/kyc/referencia).


---

# Ambientes e configuração da integração KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/ambientes

## Cada ambiente tem seu contexto

Configure a base da API, a chave e o receptor de webhooks como um conjunto. Uma transação pertence ao Tenant e ambiente resolvidos pela credencial. Um identificador válido em outro ambiente não concede acesso e pode resultar em `404`.

```text
KYC_API_BASE_URL=https://api.example.invalid
KYC_API_KEY=REPLACE_ON_YOUR_SERVER
KYC_WEBHOOK_SECRET=REPLACE_ON_YOUR_SERVER
```

Esses valores são placeholders. Não copie endereços de exemplos como se comprovassem um ambiente ativo. Confirme os endereços e acessos oficiais durante a habilitação da sua integração.

## Separe também o seu sistema

Use configurações independentes para banco de dados, filas e callbacks. Inclua o ambiente na chave de associação local da transação. Uma rotina de teste nunca deve ativar crédito, liberar uma conta ou alterar contratos reais.

Utilize dados sintéticos nos testes de contrato. Quando a homologação exigir uma jornada real, combine finalidade, acesso e tratamento dos dados com os responsáveis pela operação. Não envie documentos pessoais ao simulador público: ele trabalha apenas com cenários fixos no navegador.

## Antes da mudança de ambiente

Confirme credenciais, permissões de download, configuração de callback, segredo HMAC e regras de continuidade. Verifique também quais IPs e URLs são autorizados. Reexecute seus testes de rejeição, expiração e reconciliação; uma conexão bem-sucedida não substitui a validação funcional. Consulte o [checklist de produção](/pt-BR/desenvolvedores/kyc/entrada-em-producao).


---

# Crie uma transação de verificação de identidade

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/criar-transacao

## Defina o processo que será verificado

Crie a transação com `POST /v1/face-auth/transactions`. Os campos obrigatórios são `country_code` e `subject`; dentro de `subject`, informe `identity` com `type`, `number` e `country`. Consulte os países e locales aceitos na referência, sem transformar a lista de idiomas do site na lista de valores da API.

Criar transação KYC

### cURL

```bash
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
set -eu
: "${KYC_API_BASE_URL:?Set authorized base}" "${KYC_API_KEY:?Set API key}"
: "${KYC_IDEMPOTENCY_KEY:?Persist the key for this creation intent}"
curl --fail-with-body --max-time 15 --request POST \
  "${KYC_API_BASE_URL%/}/v1/face-auth/transactions" \
  --header "X-API-Key: ${KYC_API_KEY}" \
  --header "Idempotency-Key: ${KYC_IDEMPOTENCY_KEY}" \
  --header 'Content-Type: application/json' \
  --data-binary '{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}'
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### JavaScript / Node.js

```javascript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name) {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions", {
  method: "POST",
  headers: { "X-API-Key": required("KYC_API_KEY"), "Content-Type": "application/json", "Idempotency-Key": required("KYC_IDEMPOTENCY_KEY") },
  body: "{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}",
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 201) throw new Error("KYC HTTP " + response.status);
const transaction = await response.json();
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### TypeScript / Node.js

```typescript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions", {
  method: "POST",
  headers: { "X-API-Key": required("KYC_API_KEY"), "Content-Type": "application/json", "Idempotency-Key": required("KYC_IDEMPOTENCY_KEY") },
  body: "{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}",
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 201) throw new Error("KYC HTTP " + response.status);
const transaction: unknown = await response.json();
// Validate the response against the curated OpenAPI before using it.
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Python

```python
import json
import os
from urllib.parse import quote
from urllib.request import Request, urlopen
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
base = os.environ["KYC_API_BASE_URL"].rstrip("/")
request = Request(base + "/v1/face-auth/transactions",
    method="POST",
    headers={"X-API-Key": os.environ["KYC_API_KEY"], "Content-Type": "application/json",
             "Idempotency-Key": os.environ["KYC_IDEMPOTENCY_KEY"]},
    data="{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}".encode("utf-8"),
)
with urlopen(request, timeout=15) as response:
    if response.status != 201:
        raise RuntimeError("KYC HTTP " + str(response.status))
    transaction = json.load(response)
    print("HTTP " + str(response.status))
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### PHP

```php
<?php
// PHP 8+, standard HTTP streams. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(string $name): string {
    $value = getenv($name);
    if ($value === false || $value === '') throw new RuntimeException("Missing configuration: " . $name);
    return $value;
}
$url = rtrim(required('KYC_API_BASE_URL'), '/') . '/v1/face-auth/transactions';
$headers = ['X-API-Key: ' . required('KYC_API_KEY'), 'Content-Type: application/json', 'Idempotency-Key: ' . required('KYC_IDEMPOTENCY_KEY')];
$context = stream_context_create(['http' => ['method' => 'POST', 'header' => implode("\r\n", $headers),
    'timeout' => 15, 'ignore_errors' => true, 'content' => '{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}']]);
$body = file_get_contents($url, false, $context);
if ($body === false) throw new RuntimeException('KYC transport failure');
if (!preg_match('/^HTTP\/\S+ ([0-9]{3})/', $http_response_header[0] ?? '', $matches)) throw new RuntimeException('Invalid HTTP response');
$status = (int) $matches[1];
if ($status !== 201) throw new RuntimeException('KYC HTTP ' . $status);
$transaction = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo 'HTTP ' . $status . PHP_EOL;
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### C# / .NET

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
// .NET 8+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
static string Required(string name) => Environment.GetEnvironmentVariable(name) is { Length: > 0 } value ? value : throw new Exception("Missing configuration: " + name);
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
using var request = new HttpRequestMessage(HttpMethod.Post, Required("KYC_API_BASE_URL").TrimEnd('/') + "/v1/face-auth/transactions");
request.Headers.Add("X-API-Key", Required("KYC_API_KEY"));
request.Headers.Add("Idempotency-Key", Required("KYC_IDEMPOTENCY_KEY"));
request.Content = new StringContent("{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
if ((int)response.StatusCode != 201) throw new Exception("KYC HTTP " + (int)response.StatusCode);
using var transaction = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine("HTTP " + (int)response.StatusCode);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Java

```java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
// Java 17+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
class Example {
  static String required(String name) {
    String value = System.getenv(name);
    if (value == null || value.isEmpty()) throw new IllegalStateException("Missing configuration: " + name);
    return value;
  }
  public static void main(String[] args) throws Exception {
    String base = required("KYC_API_BASE_URL").replaceAll("/+$", "");
    String url = base + "/v1/face-auth/transactions";
    var builder = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(15))
        .header("X-API-Key", required("KYC_API_KEY"));
    builder.header("Idempotency-Key", required("KYC_IDEMPOTENCY_KEY"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString("{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}", StandardCharsets.UTF_8));
    var response = HttpClient.newHttpClient().send(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
    if (response.statusCode() != 201) throw new IllegalStateException("KYC HTTP " + response.statusCode());
    System.out.println("HTTP " + response.statusCode());
    // Parse response.body() with your application's JSON parser and validate against OpenAPI.
    // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
  }
}
```

### Go

```go
package main
import (
  "encoding/json"
  "fmt"
  "net/http"
  "net/url"
  "os"
  "strings"
  "time"
)
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
func required(name string) string {
  value := os.Getenv(name); if value == "" { panic("Missing configuration: " + name) }; return value
}
func main() {
  _ = url.PathEscape // Used by the GET variant.
  endpoint := strings.TrimRight(required("KYC_API_BASE_URL"), "/") + "/v1/face-auth/transactions"
  request, err := http.NewRequest("POST", endpoint, strings.NewReader(`{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}`)); if err != nil { panic(err) }
  request.Header.Set("X-API-Key", required("KYC_API_KEY"))
  request.Header.Set("Content-Type", "application/json")
  request.Header.Set("Idempotency-Key", required("KYC_IDEMPOTENCY_KEY"))
  response, err := (&http.Client{Timeout: 15*time.Second}).Do(request); if err != nil { panic(err) }; defer response.Body.Close()
  if response.StatusCode != 201 { panic(fmt.Sprintf("KYC HTTP %d", response.StatusCode)) }
  var transaction map[string]any
  if err := json.NewDecoder(response.Body).Decode(&transaction); err != nil { panic(err) }
  fmt.Printf("HTTP %d\n", response.StatusCode)
  // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
}
```

## Correlacione sem duplicar dados

Use `external_id` para relacionar o processo no seu sistema. `subject.tenant_subject_id` identifica a pessoa na sua base. Eles têm funções distintas e não substituem o `transaction_id` retornado pela API.

`source` descreve sistema, integração e canal de origem. `metadata` permite contexto de negócio, com limite de 8 KB; evite dados sensíveis e informações desnecessárias. A expiração pode ser configurada por `expires_in_minutes`, respeitando o contrato; quando ausente, vale o padrão do Tenant.

## Trate a resposta

O retorno documentado é `201`, inclusive quando uma criação é recuperada por idempotência. Persista o identificador antes de encaminhar a pessoa para a jornada. Se a resposta se perder após o envio, repita a mesma criação com a mesma chave e o mesmo payload; não gere uma chave nova automaticamente.

`callback_url`, quando informado, define o webhook daquela transação; quando ausente, utiliza a configuração do Tenant. Veja [idempotência](/pt-BR/desenvolvedores/kyc/idempotencia) e [webhooks](/pt-BR/desenvolvedores/kyc/webhooks-e-eventos) antes de colocar esse fluxo em produção.


---

# Encaminhe a pessoa para a jornada hospedada

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/jornada-hospedada

## Use a URL devolvida pela API

Depois de criar a transação, use o link de jornada retornado pela resposta, como `links.webview_url`. Não invente a URL concatenando `transaction_id`, CPF ou código do cliente. As URLs de exemplo da documentação são sintéticas e não identificam uma pessoa real.

A pessoa realiza a verificação em um navegador compatível. Seu aplicativo não precisa receber nem guardar a chave da API para abrir esse fluxo. A chave permanece no servidor que criou a transação.

## Trate o link como acesso sensível

Compartilhe o link apenas com a pessoa e pelos canais previstos no seu processo. Não coloque a URL completa em ferramentas públicas de analytics, logs sem controle ou mensagens de suporte abertas. Evite expor também o link curto: ele continua sendo uma forma de acesso à jornada.

As etapas exigidas dependem da configuração aplicável à transação. Não substitua uma etapa obrigatória por uma tela local e não marque a jornada como concluída porque a pessoa fechou o navegador.

## Acompanhe sem adivinhar

A abertura de um link não equivale a uma aprovação. Leia a transação pelo backend e processe eventos autenticados. Uma interrupção pode deixar o fluxo pendente; uma expiração exige uma decisão no seu processo, sem reutilizar resultados de outra transação.

Veja [resultados e status](/pt-BR/desenvolvedores/kyc/resultados-e-status) para decidir como continuar e [webhooks](/pt-BR/desenvolvedores/kyc/webhooks-e-eventos) para receber atualizações sem depender da tela da pessoa.


---

# Interprete resultados, decisões e status do KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/resultados-e-status

## Leia a transação como um conjunto

`status` descreve o andamento da jornada. `decision` expressa `pending`, `approved` ou `rejected`. `decision_reason`, quando presente, acrescenta o motivo documentado. Consulte a referência para o objeto completo e os enums vigentes; não infira a decisão apenas pelo nome de uma etapa.

| Cenário | Tratamento no seu sistema |
| --- | --- |
| `created` e etapas intermediárias | Mantenha o processo pendente |
| `manual_review_pending` | Aguarde o resultado da revisão; não libere como aprovado |
| `approved` com `decision=approved` | Aplique suas regras para continuar o processo |
| `rejected` com `decision=rejected` | Trate a recusa conforme seu procedimento |
| `expired` | Encerre ou reinicie o processo conforme sua política |

Estados como `liveness_passed` ou `document_passed` descrevem uma etapa concluída e não substituem a decisão final. Uma conexão interrompida também não é evidência de rejeição ou aprovação.

## Sincronize sua decisão de negócio

Armazene a relação entre seu processo e a transação. Ao receber um evento, valide a origem, reconheça duplicidades e consulte o estado atual antes de executar efeitos irreversíveis. Eventos antigos não devem rebaixar uma decisão mais recente por ordem de chegada.

A aprovação de KYC não determina aprovação de crédito, ausência de fraude em uma compra ou validade de poderes de representação. Essas análises continuam na sua operação. Confira [reentrega e reconciliação](/pt-BR/desenvolvedores/kyc/reentrega-e-reconciliacao) e teste todos esses cenários no [simulador](/pt-BR/desenvolvedores/kyc/simulador).


---

# Idempotência: repita a criação sem duplicar a transação

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/idempotencia

## Uma chave para a mesma intenção

Envie `Idempotency-Key` ao criar uma transação. O header é opcional no contrato, mas é recomendado para tolerar uma conexão perdida depois que o servidor recebeu seu pedido. Gere e persista a chave antes do primeiro POST; reutilize-a somente para a mesma criação e o mesmo payload.

```http
Idempotency-Key: onboarding-demo-0001
```

## Resposta perdida não significa criação perdida

Imagine que seu servidor envia o pedido e recebe um timeout. A transação pode ter sido criada. Repetir com uma chave nova pode criar uma segunda jornada. Repetir o mesmo POST com a mesma chave e payload permite recuperar a criação idempotente; o retorno documentado continua sendo `201`.

Use `external_id` para correlação de negócio, sem tratá-lo como substituto automático do header. Mantenha a associação entre chave, tentativa e transação no seu sistema.

## Trate o conflito explicitamente

A mesma chave com payload diferente resulta em `409` com código `idempotency_conflict`. Não contorne esse retorno trocando a chave silenciosamente. Descubra se o cliente alterou os dados ou se sua aplicação reconstruiu a requisição de forma inconsistente.

Uma intenção realmente nova recebe uma chave nova. Uma repetição técnica preserva a intenção anterior. Não presuma uma janela de retenção da chave que não esteja documentada para seu contrato.

A idempotência da criação não substitui a deduplicação de [webhooks](/pt-BR/desenvolvedores/kyc/webhooks-e-eventos). Para eventos, use `event_id` e mantenha efeitos de negócio também idempotentes.


---

# Receba webhooks e acompanhe os eventos de KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/webhooks-e-eventos

## Configure um receptor no seu backend

Informe `callback_url` na criação para definir o destino daquela transação. Sem esse campo, utiliza-se o webhook configurado no Tenant. O receptor deve aceitar o formato documentado e preservar os bytes recebidos antes de interpretar JSON.

O payload inclui `api_version`, `event_id`, `event_type`, `created_at` e `transaction`. Os headers `X-Face-Auth-Event-Id`, `X-Face-Auth-Event-Type`, `X-Face-Auth-Timestamp` e `X-Face-Auth-Signature` permitem identificar e validar a entrega. Veja a [referência completa](/pt-BR/desenvolvedores/kyc/referencia) para os enums de evento.

## Uma entrega pode se repetir

Deduplicar por `transaction_id` descartaria eventos diferentes da mesma jornada. Use `event_id` com armazenamento durável e protegido contra concorrência. Verifique a assinatura antes de confiar no evento e confirme a consistência dos identificadores de evento do envelope e do header.

O receptor pode guardar o evento validado em uma fila durável e processá-lo separadamente. Responda com sucesso apenas depois de aceitar o trabalho de forma persistente; uma confirmação não deve esconder uma perda de dados.

## Não dependa da ordem de chegada

Uma repetição ou atraso pode fazer um evento antigo chegar depois de outro mais recente. Consulte o GET da transação para reconciliar seu estado antes de liberar efeitos de negócio. Faça esses efeitos também idempotentes, para que uma nova tentativa não gere duas liberações.

Continue em [validação HMAC](/pt-BR/desenvolvedores/kyc/validar-assinatura-hmac) e [reentrega e reconciliação](/pt-BR/desenvolvedores/kyc/reentrega-e-reconciliacao). Uma integração pronta trata também assinatura inválida, indisponibilidade temporária e eventos recebidos fora de ordem.


---

# Valide a assinatura HMAC dos webhooks

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/validar-assinatura-hmac

## Preserve os bytes antes de interpretar JSON

A assinatura usa exatamente `timestamp + '.' + raw_body`, com HMAC SHA-256 e o segredo de webhook do seu ambiente. Leia o timestamp de `X-Face-Auth-Timestamp` e a assinatura de `X-Face-Auth-Signature`, no formato `sha256=<hex>`.

Não use `JSON.stringify` ou outra serialização do objeto recebido para reconstruir a mensagem. Espaços, quebras de linha ou ordem de propriedades alteram os bytes. Configure seu framework para disponibilizar o corpo bruto antes do parser JSON.

## Verificação no backend

O exemplo recebe uma tolerância temporal definida pela sua política; ela não é uma promessa de prazo de entrega. Mantenha o relógio do servidor sincronizado e avalie reentregas junto à política de replay.

Verificar assinatura do webhook

### JavaScript / Node.js

```javascript
import { createHmac, timingSafeEqual } from "node:crypto";
// Read X-Face-Auth-Timestamp / X-Face-Auth-Signature. rawBody is the exact received Buffer.
export function verifyWebhook(rawBody, timestamp, signature, secret, maxAgeSeconds, nowSeconds = Date.now() / 1000) {
  if (!Buffer.isBuffer(rawBody) || !secret || !Number.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0 || !Number.isFinite(nowSeconds)) return false;
  if (typeof timestamp !== "string" || !/^[0-9]{1,16}$/.test(timestamp) || typeof signature !== "string" || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
  const sentAt = Number(timestamp);
  if (!Number.isSafeInteger(sentAt) || Math.abs(nowSeconds - sentAt) > maxAgeSeconds) return false;
  const expected = createHmac("sha256", secret).update(Buffer.from(timestamp + ".", "utf8")).update(rawBody).digest();
  const received = Buffer.from(signature.slice(7), "hex");
  return expected.length === received.length && timingSafeEqual(expected, received);
}
// Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.
```

### TypeScript / Node.js

```typescript
import { createHmac, timingSafeEqual } from "node:crypto";
// Read X-Face-Auth-Timestamp / X-Face-Auth-Signature. rawBody is the exact received Buffer.
export function verifyWebhook(rawBody: Buffer, timestamp: string, signature: string, secret: string, maxAgeSeconds: number, nowSeconds = Date.now() / 1000): boolean {
  if (!Buffer.isBuffer(rawBody) || !secret || !Number.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0 || !Number.isFinite(nowSeconds)) return false;
  if (typeof timestamp !== "string" || !/^[0-9]{1,16}$/.test(timestamp) || typeof signature !== "string" || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
  const sentAt = Number(timestamp);
  if (!Number.isSafeInteger(sentAt) || Math.abs(nowSeconds - sentAt) > maxAgeSeconds) return false;
  const expected = createHmac("sha256", secret).update(Buffer.from(timestamp + ".", "utf8")).update(rawBody).digest();
  const received = Buffer.from(signature.slice(7), "hex");
  return expected.length === received.length && timingSafeEqual(expected, received);
}
// Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.
```

### Python

```python
from __future__ import annotations
import hashlib
import hmac
import math
import re
import time

def verify_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str,
                   max_age_seconds: float, now_seconds: float | None = None) -> bool:
    now = time.time() if now_seconds is None else now_seconds
    if not isinstance(raw_body, bytes) or not secret or not math.isfinite(max_age_seconds) or max_age_seconds <= 0 or not math.isfinite(now):
        return False
    if not isinstance(timestamp, str) or not re.fullmatch(r"[0-9]{1,16}", timestamp):
        return False
    if not isinstance(signature, str) or not re.fullmatch(r"sha256=[a-f0-9]{64}", signature):
        return False
    sent_at = int(timestamp)
    if sent_at > 9007199254740991 or abs(now - sent_at) > max_age_seconds:
        return False
    expected = "sha256=" + hmac.new(secret.encode("utf-8"), timestamp.encode("utf-8") + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.
```

### PHP

```php
<?php
// rawBody is the unchanged byte string from php://input, before json_decode.
function verifyWebhook(string $rawBody, string $timestamp, string $signature, string $secret, float $maxAgeSeconds, ?float $nowSeconds = null): bool {
    $now = $nowSeconds ?? microtime(true);
    if ($secret === '' || !is_finite($maxAgeSeconds) || $maxAgeSeconds <= 0 || !is_finite($now)) return false;
    if (!preg_match('/^[0-9]{1,16}$/D', $timestamp) || !preg_match('/^sha256=[a-f0-9]{64}$/D', $signature)) return false;
    $sentAt = (float) $timestamp;
    if ($sentAt > 9007199254740991 || abs($now - $sentAt) > $maxAgeSeconds) return false;
    $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($expected, $signature);
}
// Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.
```

### C# / .NET

```csharp
using System;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
// rawBody must be the unchanged request bytes, before JSON deserialization.
public static class Webhook {
  public static bool Verify(byte[] rawBody, string timestamp, string signature, string secret, double maxAgeSeconds, double? nowSeconds = null) {
    double now = nowSeconds ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000.0;
    if (rawBody is null || string.IsNullOrEmpty(secret) || !double.IsFinite(maxAgeSeconds) || maxAgeSeconds <= 0 || !double.IsFinite(now)) return false;
    if (timestamp is null || signature is null || !Regex.IsMatch(timestamp, @"\A[0-9]{1,16}\z") || !Regex.IsMatch(signature, @"\Asha256=[a-f0-9]{64}\z")) return false;
    if (!long.TryParse(timestamp, out long sentAt) || sentAt > 9007199254740991L || Math.Abs(now - sentAt) > maxAgeSeconds) return false;
    byte[] prefix = Encoding.UTF8.GetBytes(timestamp + ".");
    byte[] signed = new byte[prefix.Length + rawBody.Length];
    Buffer.BlockCopy(prefix, 0, signed, 0, prefix.Length);
    Buffer.BlockCopy(rawBody, 0, signed, prefix.Length, rawBody.Length);
    byte[] expected = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), signed);
    return CryptographicOperations.FixedTimeEquals(expected, Convert.FromHexString(signature[7..]));
  }
}
// Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.
```

### Java

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
// Java 17+. rawBody is the original request byte[], before JSON parsing.
class Webhook {
  static boolean verify(byte[] rawBody, String timestamp, String signature, String secret, double maxAgeSeconds, double nowSeconds) throws Exception {
    if (rawBody == null || secret == null || secret.isEmpty() || !Double.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0 || !Double.isFinite(nowSeconds)) return false;
    if (timestamp == null || signature == null || !timestamp.matches("[0-9]{1,16}") || !signature.matches("sha256=[a-f0-9]{64}")) return false;
    long sentAt = Long.parseLong(timestamp);
    if (sentAt > 9007199254740991L || Math.abs(nowSeconds - sentAt) > maxAgeSeconds) return false;
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    mac.update((timestamp + ".").getBytes(StandardCharsets.UTF_8));
    byte[] expected = mac.doFinal(rawBody);
    return MessageDigest.isEqual(expected, HexFormat.of().parseHex(signature.substring(7)));
  }
}
// Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.
```

### Go

```go
package main
import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "math"
  "regexp"
  "strconv"
)
// rawBody contains the exact request bytes, before json.Unmarshal. nowSeconds comes from your clock.
func verifyWebhook(rawBody []byte, timestamp, signature, secret string, maxAgeSeconds, nowSeconds float64) bool {
  if len(secret) == 0 || math.IsNaN(maxAgeSeconds) || math.IsInf(maxAgeSeconds, 0) || maxAgeSeconds <= 0 || math.IsNaN(nowSeconds) || math.IsInf(nowSeconds, 0) { return false }
  if !regexp.MustCompile("^[0-9]{1,16}$").MatchString(timestamp) || !regexp.MustCompile("^sha256=[a-f0-9]{64}$").MatchString(signature) { return false }
  sentAt, err := strconv.ParseInt(timestamp, 10, 64)
  if err != nil || sentAt > 9007199254740991 || math.Abs(nowSeconds-float64(sentAt)) > maxAgeSeconds { return false }
  mac := hmac.New(sha256.New, []byte(secret)); mac.Write([]byte(timestamp + ".")); mac.Write(rawBody)
  received, err := hex.DecodeString(signature[7:]); if err != nil { return false }
  return hmac.Equal(mac.Sum(nil), received)
}
// Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.
```

## Depois da validação

Somente após validar o HMAC, interprete JSON, valide o envelope e processe o evento. Confira o identificador de evento do header e do corpo. Deduplicate por `event_id` em armazenamento durável; a verificação criptográfica sozinha não impede repetir o mesmo efeito de negócio.

Os exemplos isolam a verificação e não implementam um receptor completo. Adicione limites de corpo, tratamento de exceções, persistência, fila, proteção de logs e [reconciliação](/pt-BR/desenvolvedores/kyc/reentrega-e-reconciliacao). Nunca registre o segredo, o corpo com dados pessoais ou links de jornada em ferramentas públicas.


---

# Reentrega, duplicidades e reconciliação de eventos

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/reentrega-e-reconciliacao

## Entrega e processamento são problemas diferentes

Uma entrega pode falhar, repetir ou chegar atrasada. O contrato prevê estados de entrega como `retry_pending` e `failed`; não presuma um intervalo fixo de repetição ou entrega exatamente uma vez. Consulte as condições operacionais da sua integração.

1. Valide assinatura e envelope antes de confiar no evento.
2. Persista a aceitação com `event_id` único, evitando corrida entre receptores.
3. Processe a continuidade de negócio de forma idempotente.
4. Consulte o GET da transação para reconciliar o estado atual.

Se seu receptor confirmar antes de persistir e cair em seguida, o trabalho pode se perder. Se persistir e a resposta de confirmação se perder, a entrega pode se repetir. Seu desenho precisa lidar com os dois casos.

## Não use a ordem de chegada como verdade

Um evento `created` atrasado não deve substituir um estado final já conhecido. O identificador da transação serve para localizar a jornada; `event_id` identifica cada evento. Consulte `GET /v1/face-auth/transactions/{transaction_id}` com a chave do Tenant e ambiente corretos.

## Prepare recuperação operacional

Registre identificador, tipo do evento, resultado do processamento e falha sanitizada. Não copie corpos pessoais ou credenciais para logs abertos. Defina como sua equipe identifica pendências, executa nova consulta e evita repetir liberações, cobranças ou outras ações.

Teste um evento duplicado, dois eventos fora de ordem, uma falha antes da persistência e outra depois dela. O [simulador](/pt-BR/desenvolvedores/kyc/simulador) ajuda a compreender cenários; sua infraestrutura de filas e armazenamento precisa de testes próprios.


---

# Documentos, assets e evidências com acesso autorizado

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/documentos-e-evidencias

## Arquivos autorizados na integração

A API Tenant permite listar assets autorizados em `/assets` e solicitar o conteúdo permitido em `/assets/{asset_id}/content`, após `/v1/face-auth/transactions/{transaction_id}`. Essas chamadas são feitas entre servidores, com API key, escopo `document_download` e IP autorizado.

Consulte [autenticação](/pt-BR/desenvolvedores/kyc/autenticacao) e os tipos de resposta na [referência](/pt-BR/desenvolvedores/kyc/referencia). Use os identificadores retornados pela listagem; a autorização do conteúdo é verificada separadamente e a listagem não libera evidências internas.

## ZIP e PDF de evidências são restritos

As rotas `/documents/download` e `/evidence.pdf` não oferecem downloads à integração Tenant. Mesmo com chave válida, escopo e IP autorizado, retornam `403 evidence_access_restricted`. O acesso a essas evidências é reservado ao Admin interno. As rotas aparecem na referência para explicar a restrição e não são incluídas nas requisições Postman.

Não repita a chamada nem amplie a chave para tentar contornar essa recusa. Conhecer um link ou identificador não concede acesso. Erros anteriores de autenticação, escopo ou IP podem ser retornados antes da restrição de evidências.

## Proteja o conteúdo permitido

A chave deve pertencer ao mesmo Tenant e ambiente da transação. Transfira somente o conteúdo autorizado e necessário à finalidade aprovada. Controle quem pode abrir, exportar e compartilhar arquivos na sua aplicação; evite caches públicos e URLs permanentes sem autorização. Cópias com dados pessoais também exigem controles e uma política de retenção adequada.

Para diagnóstico, envie ao suporte identificadores e erros sanitizados, nunca credenciais ou documentos por canais abertos. Os registros disponíveis não constituem garantia universal de conformidade nem substituem a avaliação jurídica da contratação.


---

# Erros e diagnóstico da integração KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/erros-e-diagnostico

## Comece pelo código, pela operação e pelo ambiente

As respostas de erro documentadas usam um objeto `detail` com `code` e `message`. Valide também o status HTTP. Não trate um JSON recebido com erro como se fosse uma transação criada.

| Código | Verificação recomendada |
| --- | --- |
| `validation_error` | Campos obrigatórios, tipos, país, locale e limites do contrato |
| `invalid_api_key` | Credencial, revogação e ambiente |
| `tenant_environment_disabled` | Habilitação do Tenant e ambiente |
| `transaction_not_found` | Identificador, Tenant e ambiente corretos |
| `idempotency_conflict` | Mesma chave usada com payload diferente |
| `tenant_ip_not_authorized` | IP de saída autorizado para a operação |
| `api_key_scope_not_allowed` | Permissões da chave para o recurso |

## Separe falha de conexão de recusa do contrato

Uma resposta perdida na criação deixa o resultado incerto. Use a mesma chave de idempotência e o mesmo payload, conforme o [guia de idempotência](/pt-BR/desenvolvedores/kyc/idempotencia). Erros determinísticos de credencial, escopo ou payload precisam de correção; repetição ilimitada só esconde a causa.

Para indisponibilidade temporária, defina timeouts e uma política de repetição limitada no seu cliente. Não invente uma promessa de disponibilidade ou um limite universal de tentativas.

## Compartilhe um diagnóstico útil

Registre horário, ambiente, método, caminho sem segredo, status HTTP e código sanitizado. Quando houver, associe o identificador da transação ao chamado de suporte. Remova a chave, o segredo de webhook, dados pessoais e links de jornada. Compare sua chamada com a [referência](/pt-BR/desenvolvedores/kyc/referencia) e os [exemplos](/pt-BR/desenvolvedores/kyc/exemplos-e-downloads) antes de alterar o fluxo de negócio.


---

# Limites do contrato e boas práticas de integração

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/limites-e-boas-praticas

## Valide antes de enviar

Use os limites definidos no OpenAPI, sem criar um segundo contrato em sua aplicação. Na criação, `external_id` aceita até 128 caracteres; `Idempotency-Key`, até 180; `metadata`, até 8 KB. O prazo `expires_in_minutes`, quando informado, varia entre 5 e 129600 minutos. Ausência de um campo opcional não é o mesmo que enviar um valor arbitrário.

Países, locales, estados e códigos devem ser lidos da referência vigente. O idioma `en` do site, por exemplo, não autoriza substituir automaticamente um enum de API por esse mesmo valor.

## Controle o comportamento do cliente

Defina timeouts de conexão e resposta. Limite concorrência, monitore sua fila e use repetição com espera progressiva quando a causa for transitória. Corrija erros de autenticação, permissão ou payload antes de repetir. Não faça consulta contínua sem intervalo para cada transação.

Este guia não publica uma quota universal de requisições ou um SLA. Confirme os limites operacionais e comerciais aplicáveis à sua integração com o time responsável.

## Reduza o que precisa proteger

Envie apenas dados necessários ao processo e não utilize `metadata` como depósito de documentos, segredos ou payloads pessoais extensos. Evite logar o corpo completo das requisições. Proteja links de jornada e arquivos baixados da mesma forma que outros acessos sensíveis.

Combine [webhooks](/pt-BR/desenvolvedores/kyc/webhooks-e-eventos) com [reconciliação](/pt-BR/desenvolvedores/kyc/reentrega-e-reconciliacao), em vez de depender exclusivamente de polling frequente. Revise o [checklist de produção](/pt-BR/desenvolvedores/kyc/entrada-em-producao) para ligar essas práticas à sua operação.


---

# Checklist para colocar sua integração KYC em produção

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/entrada-em-producao

## Confirme o ambiente e o acesso

- Endereço oficial da API confirmado para seu ambiente habilitado.
- Chave armazenada somente no backend e com permissões necessárias.
- Credenciais e dados separados entre testes e produção.
- IPs e escopo de download autorizados quando houver acesso a arquivos.

## Cubra o ciclo completo

Crie uma transação, persista sua correlação, abra a jornada retornada e consulte o resultado. Valide também rejeição, expiração e revisão manual; testar apenas a aprovação deixa decisões importantes sem tratamento. Uma etapa intermediária não libera o processo como aprovado.

Simule perda de resposta no POST e confirme a repetição com a mesma chave de idempotência. Envie eventos duplicados e fora de ordem ao seu consumidor e confirme que o GET reconcilia o estado sem repetir efeitos de negócio.

## Verifique o receptor de eventos

- Corpo bruto preservado antes do parser JSON.
- HMAC e política temporal validados antes do processamento.
- Aceitação durável, deduplicação por `event_id` e recuperação de falhas.
- Logs sanitizados e segredos ausentes de URLs e ferramentas públicas.

## Prepare a operação

Defina quem acompanha pendências, falhas de entrega e transações expiradas. Documente como interromper a continuidade de negócio durante uma indisponibilidade, como reconciliar depois e como entrar em contato com suporte sem compartilhar dados pessoais desnecessários.

Confirme os requisitos de finalidade, acesso e retenção com as áreas responsáveis. O simulador valida compreensão do contrato; não certifica sua infraestrutura nem representa uma verificação real. A liberação de produção depende da habilitação e da validação operacional acordadas para sua integração.


---

# Histórico e evolução do contrato público KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/historico-de-versoes

## 16 de outubro de 2026 — portal de desenvolvedores

Esta edição da documentação organiza o contrato B2B KYC em guias, referência, downloads e simulação local. A especificação de origem declara `info.version=1.0.0`. Essa versão identifica o contrato documentado; a publicação de guias não anuncia uma mudança de comportamento na API.

A referência pública documenta quatro operações Tenant suportadas e duas rotas legadas de evidência que retornam `403 evidence_access_restricted`, reservadas ao Admin interno. O Postman inclui apenas as quatro operações suportadas. Não publicamos operações de Admin, sessões internas ou controles de infraestrutura. O exportador combina a fonte curada com a restrição verificada no código atual, sem importar a aplicação.

## Como avaliar uma atualização

Ao atualizar seu consumidor, compare campos obrigatórios, enums, respostas de erro e autorização. Preserve o tratamento de valores documentados e não associe a versão do artigo à versão de um ambiente operacional.

Reexecute testes de criação idempotente, leitura, resultados e webhooks. Verifique os bytes usados no HMAC e a deduplicação por evento. Se sua aplicação depende de downloads, confirme também escopos, IPs e tipos de arquivo.

## Mantenha um registro da sua integração

Guarde a versão da especificação utilizada e a data da revisão do seu cliente. Uma mudança em um guia editorial não exige automaticamente uma migração. Alterações incompatíveis precisam de orientação específica de evolução do contrato.

Baixe a [especificação OpenAPI](/downloads/kyc/openapi.json), consulte os [exemplos](/pt-BR/desenvolvedores/kyc/exemplos-e-downloads) e use a [referência](/pt-BR/desenvolvedores/kyc/referencia) para confirmar o comportamento documentado antes de alterar seu código.


---

# Exemplos de API e downloads para integração KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/exemplos-e-downloads

## Arquivos do contrato público

- [Especificação OpenAPI em JSON](/downloads/kyc/openapi.json)
- [Coleção Postman](/downloads/kyc/postman-collection.json)
- [Ambiente Postman sem credenciais](/downloads/kyc/postman-environment.json)

Importe os arquivos e configure somente no seu ambiente privado a URL oficial e a chave habilitada. Não publique um workspace com segredos. Os exemplos abaixo usam dados fictícios; os timeouts são escolhas demonstrativas do cliente, não um SLA.

## Exemplos HTTP no backend

Criar transação KYC

### cURL

```bash
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
set -eu
: "${KYC_API_BASE_URL:?Set authorized base}" "${KYC_API_KEY:?Set API key}"
: "${KYC_IDEMPOTENCY_KEY:?Persist the key for this creation intent}"
curl --fail-with-body --max-time 15 --request POST \
  "${KYC_API_BASE_URL%/}/v1/face-auth/transactions" \
  --header "X-API-Key: ${KYC_API_KEY}" \
  --header "Idempotency-Key: ${KYC_IDEMPOTENCY_KEY}" \
  --header 'Content-Type: application/json' \
  --data-binary '{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}'
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### JavaScript / Node.js

```javascript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name) {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions", {
  method: "POST",
  headers: { "X-API-Key": required("KYC_API_KEY"), "Content-Type": "application/json", "Idempotency-Key": required("KYC_IDEMPOTENCY_KEY") },
  body: "{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}",
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 201) throw new Error("KYC HTTP " + response.status);
const transaction = await response.json();
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### TypeScript / Node.js

```typescript
import process from "node:process";
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error("Missing configuration: " + name);
  return value;
}
const base = required("KYC_API_BASE_URL").replace(/\/$/, "");
const response = await fetch(base + "/v1/face-auth/transactions", {
  method: "POST",
  headers: { "X-API-Key": required("KYC_API_KEY"), "Content-Type": "application/json", "Idempotency-Key": required("KYC_IDEMPOTENCY_KEY") },
  body: "{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}",
  signal: AbortSignal.timeout(15_000),
});
if (response.status !== 201) throw new Error("KYC HTTP " + response.status);
const transaction: unknown = await response.json();
// Validate the response against the curated OpenAPI before using it.
console.log("HTTP " + response.status);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Python

```python
import json
import os
from urllib.parse import quote
from urllib.request import Request, urlopen
# Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
base = os.environ["KYC_API_BASE_URL"].rstrip("/")
request = Request(base + "/v1/face-auth/transactions",
    method="POST",
    headers={"X-API-Key": os.environ["KYC_API_KEY"], "Content-Type": "application/json",
             "Idempotency-Key": os.environ["KYC_IDEMPOTENCY_KEY"]},
    data="{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}".encode("utf-8"),
)
with urlopen(request, timeout=15) as response:
    if response.status != 201:
        raise RuntimeError("KYC HTTP " + str(response.status))
    transaction = json.load(response)
    print("HTTP " + str(response.status))
# Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### PHP

```php
<?php
// PHP 8+, standard HTTP streams. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
function required(string $name): string {
    $value = getenv($name);
    if ($value === false || $value === '') throw new RuntimeException("Missing configuration: " . $name);
    return $value;
}
$url = rtrim(required('KYC_API_BASE_URL'), '/') . '/v1/face-auth/transactions';
$headers = ['X-API-Key: ' . required('KYC_API_KEY'), 'Content-Type: application/json', 'Idempotency-Key: ' . required('KYC_IDEMPOTENCY_KEY')];
$context = stream_context_create(['http' => ['method' => 'POST', 'header' => implode("\r\n", $headers),
    'timeout' => 15, 'ignore_errors' => true, 'content' => '{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}']]);
$body = file_get_contents($url, false, $context);
if ($body === false) throw new RuntimeException('KYC transport failure');
if (!preg_match('/^HTTP\/\S+ ([0-9]{3})/', $http_response_header[0] ?? '', $matches)) throw new RuntimeException('Invalid HTTP response');
$status = (int) $matches[1];
if ($status !== 201) throw new RuntimeException('KYC HTTP ' . $status);
$transaction = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo 'HTTP ' . $status . PHP_EOL;
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### C# / .NET

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
// .NET 8+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
static string Required(string name) => Environment.GetEnvironmentVariable(name) is { Length: > 0 } value ? value : throw new Exception("Missing configuration: " + name);
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
using var request = new HttpRequestMessage(HttpMethod.Post, Required("KYC_API_BASE_URL").TrimEnd('/') + "/v1/face-auth/transactions");
request.Headers.Add("X-API-Key", Required("KYC_API_KEY"));
request.Headers.Add("Idempotency-Key", Required("KYC_IDEMPOTENCY_KEY"));
request.Content = new StringContent("{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
if ((int)response.StatusCode != 201) throw new Exception("KYC HTTP " + (int)response.StatusCode);
using var transaction = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine("HTTP " + (int)response.StatusCode);
// Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
```

### Java

```java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
// Java 17+. Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
class Example {
  static String required(String name) {
    String value = System.getenv(name);
    if (value == null || value.isEmpty()) throw new IllegalStateException("Missing configuration: " + name);
    return value;
  }
  public static void main(String[] args) throws Exception {
    String base = required("KYC_API_BASE_URL").replaceAll("/+$", "");
    String url = base + "/v1/face-auth/transactions";
    var builder = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(15))
        .header("X-API-Key", required("KYC_API_KEY"));
    builder.header("Idempotency-Key", required("KYC_IDEMPOTENCY_KEY"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString("{\"country_code\":\"BR\",\"external_id\":\"docs-example-0001\",\"locale\":\"pt-BR\",\"subject\":{\"identity\":{\"country\":\"BR\",\"number\":\"00000000000\",\"type\":\"CPF\"}}}", StandardCharsets.UTF_8));
    var response = HttpClient.newHttpClient().send(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
    if (response.statusCode() != 201) throw new IllegalStateException("KYC HTTP " + response.statusCode());
    System.out.println("HTTP " + response.statusCode());
    // Parse response.body() with your application's JSON parser and validate against OpenAPI.
    // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
  }
}
```

### Go

```go
package main
import (
  "encoding/json"
  "fmt"
  "net/http"
  "net/url"
  "os"
  "strings"
  "time"
)
// Server-side only. Configure KYC_API_BASE_URL with your authorized base (https://api.example.invalid is illustrative), and KYC_API_KEY privately. Never expose credentials in browser code.
func required(name string) string {
  value := os.Getenv(name); if value == "" { panic("Missing configuration: " + name) }; return value
}
func main() {
  _ = url.PathEscape // Used by the GET variant.
  endpoint := strings.TrimRight(required("KYC_API_BASE_URL"), "/") + "/v1/face-auth/transactions"
  request, err := http.NewRequest("POST", endpoint, strings.NewReader(`{"country_code":"BR","external_id":"docs-example-0001","locale":"pt-BR","subject":{"identity":{"country":"BR","number":"00000000000","type":"CPF"}}}`)); if err != nil { panic(err) }
  request.Header.Set("X-API-Key", required("KYC_API_KEY"))
  request.Header.Set("Content-Type", "application/json")
  request.Header.Set("Idempotency-Key", required("KYC_IDEMPOTENCY_KEY"))
  response, err := (&http.Client{Timeout: 15*time.Second}).Do(request); if err != nil { panic(err) }; defer response.Body.Close()
  if response.StatusCode != 201 { panic(fmt.Sprintf("KYC HTTP %d", response.StatusCode)) }
  var transaction map[string]any
  if err := json.NewDecoder(response.Body).Decode(&transaction); err != nil { panic(err) }
  fmt.Printf("HTTP %d\n", response.StatusCode)
  // Persist transaction_id and business correlation; use the returned links.webview_url. Read status, decision and decision_reason together. Reuse the SAME idempotency key AND original payload only for the same creation intent; never silently bypass a 409 conflict.
}
```


Esses trechos mostram a criação básica; não são SDKs oficiais nem consumidores completos. Acrescente validação de configuração, persistência, tratamento de erros e repetição idempotente conforme sua aplicação. Uma intenção de criação nova deve receber outra chave; uma tentativa técnica deve preservar a anterior e o mesmo payload.

Para cURL, veja o [início rápido](/pt-BR/desenvolvedores/kyc/inicio-rapido). Para o receptor de eventos, siga [validação HMAC](/pt-BR/desenvolvedores/kyc/validar-assinatura-hmac) e [reconciliação](/pt-BR/desenvolvedores/kyc/reentrega-e-reconciliacao). O [simulador](/pt-BR/desenvolvedores/kyc/simulador) demonstra cenários fixos sem acessar a API real.


---

# Explore o simulador local de KYC

Source: https://contrato.cloud/pt-BR/desenvolvedores/kyc/guia-do-simulador

## Entenda o contrato antes de integrar

O [simulador local](/pt-BR/desenvolvedores/kyc/simulador) permite explorar cenários fixos de criação e resultado. Ele funciona no navegador com dados sintéticos. Não solicita CPF, documento, selfie, chave de API ou segredo de webhook e não realiza uma verificação de identidade.

Use-o para alinhar produto e engenharia sobre o que seu sistema deve fazer quando uma jornada permanece pendente, é aprovada, rejeitada, expira ou segue para revisão manual. Observe a diferença entre `status` e `decision`, em vez de liberar um processo por uma etapa intermediária.

## Um roteiro de exploração

1. Comece pela criação e identifique o `transaction_id` sintético.
2. Selecione os resultados disponíveis e compare seus estados.
3. Relacione cada cenário à tela ou ação que seu sistema apresentará.
4. Leia como duplicidades e eventos atrasados exigem reconciliação.

Anote quais decisões precisam de intervenção humana e quais podem continuar automaticamente conforme sua política. O objetivo é transformar um payload técnico em uma regra de operação compreensível.

## O que deve ser testado na sua integração

O simulador não certifica credenciais, rede, entrega de mensagens, biometria ou persistência. Esses comportamentos exigem validação no ambiente habilitado e na infraestrutura da sua aplicação. Não envie dados reais para tentar ampliar o teste.

Continue com o [início rápido](/pt-BR/desenvolvedores/kyc/inicio-rapido), valide o consumidor de [webhooks](/pt-BR/desenvolvedores/kyc/webhooks-e-eventos) e complete o [checklist de produção](/pt-BR/desenvolvedores/kyc/entrada-em-producao).
