> ## Documentation Index
> Fetch the complete documentation index at: https://docs-platform.services-valid.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Implementação

> Inicialize o LivenessFacetecSDK v10 e dispare uma verificação de liveness com FaceTec no Android

Este guia mostra a API pública do `LivenessFacetecSDK` e o fluxo completo de uma verificação de vivacidade.

## API pública

A fachada do SDK fica em `com.vcc.vendor.facetec.sdk.LivenessFacetecSDK` e tem quatro funções:

```kotlin theme={"theme":"catppuccin-latte"}
object LivenessFacetecSDK {
    suspend fun init(
        application: Application,
        config: LivenessFacetecSDKConfig
    ): Result<Unit>

    suspend fun getSessionData(): Result<LivenessFacetecSDKSessionData>

    fun startLivenessCheck(
        activity: Activity,
        sessionId: String,
        sessionToken: String
    ): Flow<LivenessFacetecSDKLivenessState>

    fun stop()
}
```

* `init` recebe a **`Application`** (não um `Context`) e devolve `Result<Unit>`. É **idempotente** — chamadas subsequentes retornam sucesso imediatamente, sem reinicializar a FaceTec, desde que o SDK não tenha sido parado com `stop()`.
* `getSessionData` coleta o `ssid` de perimeter security, usado pelo seu backend na criação da sessão.
* `startLivenessCheck` requer uma `Activity` chamadora porque o FaceTec abre sua própria UI a partir dela.
* `stop` para a execução do SDK e libera recursos do sistema.

<Info>
  As chaves internas do SDK são embutidas no AAR em tempo de compilação. **Nenhuma chave precisa ser fornecida pelo app consumidor** em `init` ou em qualquer outra chamada.
</Info>

## Configuração

```kotlin theme={"theme":"catppuccin-latte"}
data class LivenessFacetecSDKConfig(
    val customization: LivenessFacetecSDKCustomization? = null,
    val lowLightCustomization: LivenessFacetecSDKCustomization? = null,
    val dynamicDimmingCustomization: LivenessFacetecSDKCustomization? = null,
)
```

| Parâmetro                     | Tipo                               | Obrigatório | Descrição                                                                                                                                     |
| ----------------------------- | ---------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `customization`               | `LivenessFacetecSDKCustomization?` | Não         | Personalização da UI do FaceTec aplicada em condições normais. Veja [Customização](/plataforma/liveness/facetec/v10/sdk/android/customizacao) |
| `lowLightCustomization`       | `LivenessFacetecSDKCustomization?` | Não         | Customização alternativa que a FaceTec aplica automaticamente em baixa luminosidade                                                           |
| `dynamicDimmingCustomization` | `LivenessFacetecSDKCustomization?` | Não         | Customização alternativa aplicada durante o dimming dinâmico da tela                                                                          |

## Dados de sessão

Depois de `init`, chame `getSessionData()` para obter o `ssid` — o identificador de perimeter security que seu backend repassa na criação da sessão:

```kotlin theme={"theme":"catppuccin-latte"}
data class LivenessFacetecSDKSessionData(
    val ssid: String
)
```

| Campo  | Tipo     | Descrição                                                                        |
| ------ | -------- | -------------------------------------------------------------------------------- |
| `ssid` | `String` | Identificador de sessão de dispositivo. Vem **vazio** (`""`) se a coleta falhar. |

<Info>
  A coleta é **best-effort** e nunca bloqueia o fluxo: qualquer falha interna devolve `Result.success` com `ssid` vazio, apenas registrando o erro internamente. Um `ssid` vazio **não é um erro** — siga o fluxo normalmente.
</Info>

## Criação de sessão — responsabilidade do consumer app

O SDK **não cria sessões** nem se comunica diretamente com o backend de autenticação. Após `init()`, o consumer app deve:

1. Chamar `getSessionData()` e obter o `ssid`.
2. Encaminhar o `ssid` ao seu backend, que cria a sessão e devolve `sessionId` e `sessionToken`.
3. Passar os dois tokens para `startLivenessCheck()`.

<Info>
  O SDK não tem acesso às credenciais do backend do consumer nem conhece o contexto de negócio necessário para criar uma sessão válida. Manter a criação de sessão no consumer app garante flexibilidade (autenticação, multi-tenant) sem acoplar o SDK a uma topologia específica de backend.
</Info>

O contrato da chamada de criação de sessão está documentado em [Serviço](/plataforma/liveness/facetec/v10/servico).

## Estados do `Flow`

`startLivenessCheck` retorna um `Flow<LivenessFacetecSDKLivenessState>` que emite um único caminho `Loading → Success` ou `Loading → Error` antes de completar:

```kotlin theme={"theme":"catppuccin-latte"}
sealed class LivenessFacetecSDKLivenessState {
    object Loading : LivenessFacetecSDKLivenessState()
    data class Success(val data: LivenessFacetecSDKResultData) : LivenessFacetecSDKLivenessState()
    data class Error(val data: LivenessFacetecSDKResultData) : LivenessFacetecSDKLivenessState()
}
```

| Estado          | Descrição                              |
| --------------- | -------------------------------------- |
| `Loading`       | Captura FaceTec em andamento           |
| `Success(data)` | `data.status == SESSION_COMPLETED`     |
| `Error(data)`   | `data.status` com qualquer outro valor |

<Info>
  **`Success` e `Error` carregam o mesmo tipo**, `LivenessFacetecSDKResultData`. O que os diferencia é apenas o `status`: `SESSION_COMPLETED` sempre chega como `Success`; qualquer outro valor (cancelamento pelo usuário, erro de câmera, lockout etc.) chega como `Error`. Não existe um tipo de erro separado — a causa da falha está inteiramente no `status`.
</Info>

O estado terminal é decidido pelo status que a própria FaceTec retorna ao fechar a UI de captura, e é emitido imediatamente — sem depender de nenhuma chamada de rede adicional e sem estados intermediários durante a captura.

<Info>
  **Retentativa dentro da captura:** enquanto a UI do FaceTec está aberta, ela pode pedir ao usuário que tente de novo quantas vezes julgar necessário. Esse retry é interno ao FaceTec e **não é exposto ao seu app** — o `Flow` entrega apenas o desfecho final.
</Info>

## Fluxo de uso

<Steps>
  <Step title="Inicializar o SDK">
    Chame `LivenessFacetecSDK.init` uma única vez, preferencialmente no `onCreate()` da `Application`:

    ```kotlin theme={"theme":"catppuccin-latte"}
    import android.app.Application
    import android.util.Log
    import com.vcc.vendor.facetec.sdk.LivenessFacetecSDK
    import com.vcc.vendor.facetec.sdk.models.LivenessFacetecSDKConfig
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.SupervisorJob
    import kotlinx.coroutines.launch

    class MyApplication : Application() {

        private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

        override fun onCreate() {
            super.onCreate()

            scope.launch {
                LivenessFacetecSDK.init(
                    application = this@MyApplication,
                    config = LivenessFacetecSDKConfig() // customization é opcional
                ).onSuccess {
                    Log.d("LivenessFacetecSDK", "SDK inicializado com sucesso")
                }.onFailure { e ->
                    Log.e("LivenessFacetecSDK", "Falha ao inicializar: ${e.message}")
                }
            }
        }
    }
    ```

    <Info>
      `init()` é idempotente — é seguro chamá-lo em múltiplas Activities ou no `onCreate()` da `Application`.
    </Info>
  </Step>

  <Step title="Criar sessão no backend">
    Colete o `ssid` e chame seu backend para obter os tokens necessários para a captura:

    ```kotlin theme={"theme":"catppuccin-latte"}
    val sessionData = LivenessFacetecSDK.getSessionData().getOrThrow()

    // Exemplo — adapte ao seu cliente HTTP
    val session = mySessionApi.createSession(
        body = SessionRequest(ssid = sessionData.ssid)
    )
    // session: { sessionId, sessionToken }
    ```
  </Step>

  <Step title="Iniciar a verificação">
    <Warning>
      `startLivenessCheck()` retorna um `Flow` **cold**. Se ele for coletado antes de `init()` ter retornado sucesso, a coleta lança `IllegalStateException` — a exceção surge **no momento da coleta** (`.collect { }` / `.catch { }`), não na chamada de `startLivenessCheck()` em si, já que nada roda até haver um coletor. Garanta que `init()` retornou `Result.success(...)` antes de iniciar a captura, ou trate a exceção com `.catch { }`.
    </Warning>

    ```kotlin theme={"theme":"catppuccin-latte"}
    import androidx.lifecycle.lifecycleScope
    import com.vcc.vendor.facetec.sdk.LivenessFacetecSDK
    import com.vcc.vendor.facetec.sdk.LivenessFacetecSDKLivenessState
    import kotlinx.coroutines.flow.catch
    import kotlinx.coroutines.launch

    lifecycleScope.launch {
        LivenessFacetecSDK.startLivenessCheck(
            activity = this@VerificationActivity,
            sessionId = session.sessionId,
            sessionToken = session.sessionToken
        )
            .catch { e ->
                Log.e("LivenessFacetecSDK", "Erro inesperado: ${e.message}")
            }
            .collect { state ->
                when (state) {
                    is LivenessFacetecSDKLivenessState.Loading -> mostrarProgresso()
                    is LivenessFacetecSDKLivenessState.Success -> tratarSucesso(state.data)
                    is LivenessFacetecSDKLivenessState.Error -> tratarFalha(state.data)
                }
            }
    }
    ```
  </Step>

  <Step title="Tratar o resultado">
    No caso de sucesso, use o `sessionId` para reconciliar a captura no backend. No caso de falha, use o `status` para decidir a ação e o `friendlyMessage` para exibir ao usuário — veja [Solução de problemas](/plataforma/liveness/facetec/v10/sdk/android/troubleshooting).
  </Step>
</Steps>

## Campos de `LivenessFacetecSDKResultData`

Carregado por **ambas** as variantes do `Flow` — `Success(data)` e `Error(data)` usam exatamente o mesmo tipo:

| Campo             | Tipo                              | Descrição                                                                                           |
| ----------------- | --------------------------------- | --------------------------------------------------------------------------------------------------- |
| `sessionId`       | `String`                          | Identificador da sessão — o mesmo passado para `startLivenessCheck()`                               |
| `status`          | `LivenessFacetecSDKSessionStatus` | Status final da sessão FaceTec. Veja a tabela abaixo                                                |
| `friendlyMessage` | `String`                          | Mensagem legível para o usuário final, fixa por `status`                                            |
| `ageGroup`        | `Int?`                            | Valor retornado nativamente pela FaceTec. Só preenchido quando `status == SESSION_COMPLETED`        |
| `auditTrailImage` | `String?`                         | Imagem do audit trail em base64. Só preenchida quando `status == SESSION_COMPLETED` e a API a envia |

<Info>
  `friendlyMessage` é definido no lado do cliente e é fixo por `status` — **não vem do backend**. Use-o como texto pronto para exibição, e o `status` como a chave de decisão do seu fluxo.
</Info>

## `LivenessFacetecSDKSessionStatus`

Enum fechado com os 8 valores possíveis. Apenas `SESSION_COMPLETED` resulta em `Success`; todos os outros resultam em `Error`:

| Valor                       | Situação                                                                                                            | `friendlyMessage`                                                                                                         |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `SESSION_COMPLETED`         | Sucesso — único valor que resulta em `Success`                                                                      | "The Session was performed successfully."                                                                                 |
| `REQUEST_ABORTED`           | A aplicação abortou a requisição de sessão                                                                          | "The application aborted on session request."                                                                             |
| `USER_CANCELLED_FACE_SCAN`  | Usuário cancelou antes de completar os scans de rosto necessários                                                   | "The user cancelled before performing enough Scans to Succeed."                                                           |
| `USER_CANCELLED_ID_SCAN`    | Usuário cancelou antes de completar os scans de documento necessários                                               | "The user cancelled before performing enough Scans to Complete."                                                          |
| `LOCKED_OUT`                | O limite de tentativas da sessão foi atingido                                                                       | "SDK is in a lockout state, the limit of tries for this session has been reached."                                        |
| `CAMERA_ERROR`              | A câmera selecionada não está ativa                                                                                 | "Session cancelled because selected camera is not active."                                                                |
| `CAMERA_PERMISSIONS_DENIED` | Usuário não concedeu (ou negou anteriormente) a permissão de câmera                                                 | "The user did not enable the camera after prompting for camera permissions or camera permissions were previously denied." |
| `UNKNOWN_INTERNAL_ERROR`    | Erro interno inesperado, não relacionado ao veredito de liveness (ex.: SDK parado via `stop()` durante uma captura) | "The Session was cancelled because of an Unknown Error."                                                                  |

<Info>
  Como o enum é fechado, um `when (data.status)` exaustivo compila sem `else` — e passa a falhar na build se a Valid introduzir um valor novo, o que é intencional: você fica sabendo em tempo de compilação.
</Info>

## Exemplos de retorno

### Sucesso — liveness aprovado

```kotlin theme={"theme":"catppuccin-latte"}
LivenessFacetecSDKLivenessState.Success(
    data = LivenessFacetecSDKResultData(
        sessionId = "8f3a9c10-2b7d-4e1f-9c2a-5d6e7f8a9b01",
        status = LivenessFacetecSDKSessionStatus.SESSION_COMPLETED,
        friendlyMessage = "The Session was performed successfully.",
        ageGroup = 7,
        auditTrailImage = "..."
    )
)
```

### Falha — usuário cancelou a captura

```kotlin theme={"theme":"catppuccin-latte"}
LivenessFacetecSDKLivenessState.Error(
    data = LivenessFacetecSDKResultData(
        sessionId = "8f3a9c10-2b7d-4e1f-9c2a-5d6e7f8a9b01",
        status = LivenessFacetecSDKSessionStatus.USER_CANCELLED_FACE_SCAN,
        friendlyMessage = "The user cancelled before performing enough Scans to Succeed.",
        ageGroup = null,
        auditTrailImage = null
    )
)
```

<Info>
  Cancelamento pelo usuário **não é erro técnico** — o SDK funcionou corretamente. Trate como desistência no seu app (voltar à tela anterior ou oferecer nova tentativa), sem reportar como falha do SDK.
</Info>

## Exemplo completo

```kotlin theme={"theme":"catppuccin-latte"}
package com.example.myapp

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.vcc.vendor.facetec.sdk.LivenessFacetecSDK
import com.vcc.vendor.facetec.sdk.LivenessFacetecSDKLivenessState
import com.vcc.vendor.facetec.sdk.models.LivenessFacetecSDKSessionStatus
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnStart = findViewById<Button>(R.id.btn_start)
        val progress = findViewById<ProgressBar>(R.id.progress)
        val tvResult = findViewById<TextView>(R.id.tv_result)

        btnStart.setOnClickListener {
            btnStart.isEnabled = false
            tvResult.text = ""

            lifecycleScope.launch {
                // 1. Coletar o ssid e criar a sessão no backend
                val sessionData = LivenessFacetecSDK.getSessionData().getOrNull()
                if (sessionData == null) {
                    tvResult.text = "SDK não inicializado"
                    btnStart.isEnabled = true
                    return@launch
                }

                val session = mySessionApi.createSession(
                    body = SessionRequest(ssid = sessionData.ssid)
                )

                // 2. Iniciar a captura com os tokens da sessão
                LivenessFacetecSDK.startLivenessCheck(
                    activity = this@MainActivity,
                    sessionId = session.sessionId,
                    sessionToken = session.sessionToken
                )
                    .catch { e ->
                        Log.e("MainActivity", e.message.orEmpty())
                        progress.visibility = View.GONE
                        tvResult.text = "Erro: ${e.message}"
                        btnStart.isEnabled = true
                    }
                    .collect { state ->
                        when (state) {
                            is LivenessFacetecSDKLivenessState.Loading -> {
                                progress.visibility = View.VISIBLE
                                tvResult.text = "Processando…"
                            }
                            is LivenessFacetecSDKLivenessState.Success -> {
                                progress.visibility = View.GONE
                                tvResult.text = buildString {
                                    appendLine("Liveness finalizado")
                                    appendLine("Session: ${state.data.sessionId}")
                                    appendLine("Age group: ${state.data.ageGroup ?: "n/d"}")
                                }
                                btnStart.isEnabled = true
                            }
                            is LivenessFacetecSDKLivenessState.Error -> {
                                progress.visibility = View.GONE
                                tvResult.text = when (state.data.status) {
                                    LivenessFacetecSDKSessionStatus.USER_CANCELLED_FACE_SCAN,
                                    LivenessFacetecSDKSessionStatus.USER_CANCELLED_ID_SCAN ->
                                        "Verificação cancelada"
                                    LivenessFacetecSDKSessionStatus.LOCKED_OUT ->
                                        "Limite de tentativas atingido"
                                    else -> state.data.friendlyMessage
                                }
                                btnStart.isEnabled = true
                            }
                        }
                    }
            }
        }
    }
}
```

## Timeouts de rede

O SDK usa timeouts fixos, não configuráveis pelo consumer:

| Tipo            | Valor       |
| --------------- | ----------- |
| Connect timeout | 30 segundos |
| Read timeout    | 30 segundos |
| Write timeout   | 30 segundos |

Dimensione a UX de loading e o timeout de fallback do seu app considerando esses valores. Em condições de rede ruim, o `Flow` pode permanecer em `Loading` por até 30 segundos antes de emitir o estado terminal.

## Parando o SDK

`LivenessFacetecSDK.stop()` libera os recursos internos do SDK e reseta seu estado, deixando-o como estava antes do primeiro `init()`. Uma chamada subsequente a `init()` se comporta como primeiro uso.

```kotlin theme={"theme":"catppuccin-latte"}
LivenessFacetecSDK.stop()
```

| Situação                                                    | Ação                                                                             |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Teardown de tela ou sessão (ex.: `onDestroy()` da Activity) | Chame `stop()` para liberar recursos                                             |
| Entre capturas (fluxo multi-etapa)                          | Opcional — `init()` é idempotente; use `stop()` apenas se quiser liberar memória |
| Antes de encerrar o processo                                | `stop()` garante limpeza ordeira das chaves em memória                           |

<Info>
  Se `stop()` for chamado durante uma captura em andamento, ela é cancelada imediatamente e o `Flow` em coleta recebe um `Error` com `status = UNKNOWN_INTERNAL_ERROR` — sem travar e sem exceção não tratada. Chamar `stop()` antes de `init()`, ou duas vezes seguidas, é um no-op seguro.
</Info>

## O que muda em relação à v9

| Área                 | v9                                                                                                                      | v10                                                                                                                                                                                                                          |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `init`               | `init(applicationContext: Context, config): Result<LivenessFacetecSDKInitData>`                                         | `init(application: Application, config): Result<Unit>` — recebe a `Application` e não devolve mais dados                                                                                                                     |
| Device signal        | `LivenessFacetecSDKInitData` com `visitorId`/`requestId` (FingerprintJS)                                                | `getSessionData()` devolve `ssid` (perimeter security)                                                                                                                                                                       |
| `startLivenessCheck` | `(activity, sessionId, sessionToken, facetecSessionToken)`                                                              | `(activity, sessionId, sessionToken)` — não existe mais `facetecSessionToken`                                                                                                                                                |
| Resultado            | 12 campos (`status: String`, `result`, `scanResultBlob`, `transactionId`, `replayCheck`, `livenessCheck`, entre outros) | 5 campos: `sessionId`, `status` (enum), `friendlyMessage`, `ageGroup?`, `auditTrailImage?`                                                                                                                                   |
| Veredito             | `Success` sempre; conferir `livenessCheck == true` para saber se passou                                                 | `status == SESSION_COMPLETED` chega como `Success`; qualquer outro valor chega como `Error`                                                                                                                                  |
| Erros                | `LivenessFacetecSDKError` com `CaptureError`, `SubmitError`, `NetworkError`, `Cancelled`, `Unknown`                     | O tipo **deixou de existir**. `Error` carrega o mesmo `LivenessFacetecSDKResultData` de `Success`; a causa está no `status`                                                                                                  |
| Retry                | Controlado pelo backend via `canRetry`, dentro da mesma sessão                                                          | Acontece dentro da UI do FaceTec e **não é exposto ao app** — o `Flow` emite apenas o desfecho final. Para tentar de novo depois de um `Error`, crie uma sessão nova no seu backend e chame `startLivenessCheck()` outra vez |

<Warning>
  Se o seu código v9 lia `livenessCheck`, `result`, `scanResultBlob` ou fazia `when` sobre subclasses de `LivenessFacetecSDKError`, esses caminhos precisam ser reescritos — nenhum deles existe na v10.
</Warning>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Customização" icon="palette" iconType="regular" href="/plataforma/liveness/facetec/v10/sdk/android/customizacao">
    Personalize cores, textos e animações da UI do FaceTec
  </Card>

  <Card title="Solução de problemas" icon="wrench" iconType="regular" href="/plataforma/liveness/facetec/v10/sdk/android/troubleshooting">
    Diagnóstico por status e problemas comuns de build
  </Card>
</CardGroup>
