> ## 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 e dispare uma verificação de liveness com FaceTec

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 apenas três funções:

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

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

	fun stop()
}
```

* `init` é **idempotente** — Chamadas subsequentes não reinicializam o FaceTec, mas sempre coletam o fingerprint novamente.
* `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.

## Configuração

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

| Parâmetro       | Tipo                               | Obrigatório | Descrição                                                                                                   |
| --------------- | ---------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------- |
| `customization` | `LivenessFacetecSDKCustomization?` | Não         | Personalização da UI do FaceTec. Veja [Customização](/plataforma/liveness/facetec/sdk/android/customizacao) |

### Campos de `LivenessFacetecSDKInitData`

`init()` retorna `Result<LivenessFacetecSDKInitData>` com os dados de device fingerprint coletados pelo FingerprintJS Pro:

```kotlin theme={"theme":"catppuccin-latte"}
data class LivenessFacetecSDKInitData(
    val visitorId: String?,  // Visitor ID do FingerprintJS Pro; null se falhar
    val requestId: String?   // Request ID do FingerprintJS Pro; null se falhar
)
```

| Campo       | Tipo      | Descrição                                                                              |
| ----------- | --------- | -------------------------------------------------------------------------------------- |
| `visitorId` | `String?` | Visitor ID gerado pelo FingerprintJS Pro. Encaminhe ao backend para criar a sessão.    |
| `requestId` | `String?` | Request ID da coleta do FingerprintJS Pro. Encaminhe ao backend junto com `visitorId`. |

<Warning>
  Ambos os campos são `nullable`. Trate o caso `null` — strings vazias são aceitas pelo endpoint de criação de sessão, mas o sinal de device fingerprint estará ausente.
</Warning>

## 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. Encaminhar `visitorId` e `requestId` retornados por `init()` ao seu backend.
2. O backend cria a sessão e devolve `sessionId`, `sessionToken` e `facetecSessionToken`.
3. Passar os três 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>

## 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 error: LivenessFacetecSDKError) : LivenessFacetecSDKLivenessState()
}
```

<Info>
  O SDK suporta retry controlado pelo backend. Quando o backend retorna `canRetry: true` na resposta de uma captura, o SDK mantém a UI do FaceTec aberta e realiza uma nova tentativa automaticamente — sem nenhuma ação do consumer. O Flow **não** emite estados intermediários durante as tentativas; `Success` ou `Error` são emitidos apenas ao final da última tentativa. O número de tentativas é definido exclusivamente pelo backend.
</Info>

## Fluxo de uso

<Steps>
  <Step title="Inicializar o SDK">
    Chame `LivenessFacetecSDK.init` uma única vez (preferencialmente em uma `Application` ou na tela inicial):

    ```kotlin theme={"theme":"catppuccin-latte"}
    import com.vcc.vendor.facetec.sdk.LivenessFacetecSDK
    import com.vcc.vendor.facetec.sdk.models.LivenessFacetecSDKConfig

    lifecycleScope.launch {
        LivenessFacetecSDK.init(
            applicationContext = applicationContext,
            config = LivenessFacetecSDKConfig()
        ).onSuccess { initData ->
            // Encaminhe initData.visitorId e initData.requestId ao backend
            // para criar a sessão antes de chamar startLivenessCheck
        }.onFailure { e ->
            Log.e("LivenessFacetecSDK", "Falha ao inicializar: ${e.message}")
        }
    }
    ```

    <Info>
      `init()` é idempotente — Chamadas subsequentes não reinicializam o FaceTec, mas sempre coletam o fingerprint novamente. É seguro chamá-lo em múltiplas Activities ou no `onCreate()` da `Application`.
    </Info>
  </Step>

  <Step title="Criar sessão no backend">
    Com `visitorId` e `requestId` em mãos, chame seu backend para obter os três tokens necessários para a captura:

    ```kotlin theme={"theme":"catppuccin-latte"}
    val session = mySessionApi.createSession(
        body = SessionRequest(
            visitorId = initData.visitorId.orEmpty(),
            eventId   = initData.requestId.orEmpty()
        )
    )
    // session: { sessionId, sessionToken, facetecSessionToken }
    ```
  </Step>

  <Step title="Iniciar a verificação">
    <Warning>
      `startLivenessCheck()` lança `IllegalStateException` diretamente (fora do Flow) se chamado antes de `init()` completar com sucesso. Use `.catch { }` no collector ou garanta que `init()` retornou `Result.success(...)` antes de habilitar o botão de captura.
    </Warning>

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

    lifecycleScope.launch {
        LivenessFacetecSDK.startLivenessCheck(
            activity            = this@MainActivity,
            sessionId           = session.sessionId,
            sessionToken        = session.sessionToken,
            facetecSessionToken = session.facetecSessionToken
        )
            .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 -> tratarErro(state.error)
                }
            }
    }
    ```
  </Step>

  <Step title="Tratar o resultado">
    No caso de sucesso, use `sessionId` e `captureId` para reconciliar a captura no backend. Verifique `livenessCheck == true` para confirmar aprovação biométrica. No caso de erro, mapeie cada subclasse de `LivenessFacetecSDKError` para uma mensagem ou ação adequada — veja [Solução de problemas](/plataforma/liveness/facetec/sdk/android/troubleshooting).
  </Step>
</Steps>

## Campos de `LivenessFacetecSDKResultData`

Quando a verificação termina com sucesso, o SDK emite `Success(data: LivenessFacetecSDKResultData)`:

| Campo               | Tipo    | Descrição                                       |
| ------------------- | ------- | ----------------------------------------------- |
| `sessionId`         | String  | ID da sessão criada no backend da Plataforma ID |
| `captureId`         | String  | ID único da captura submetida                   |
| `status`            | String  | Status final da captura                         |
| `processedAt`       | String  | Timestamp ISO-8601 do processamento             |
| `result`            | String? | Resultado do Liveness                           |
| `scanResultBlob`    | String? | Blob bruto retornado pelo FaceTec               |
| `transactionId`     | String? | ID da transação no provedor                     |
| `retryScreen`       | Int?    | Indicador interno de retry                      |
| `replayCheck`       | Boolean | Resultado da verificação anti-replay            |
| `sessionTokenCheck` | Boolean | Validação do token de sessão                    |
| `auditTrailCheck`   | Boolean | Validação da trilha de auditoria                |
| `livenessCheck`     | Boolean | Resultado da verificação de vivacidade          |

<Info>
  Os quatro campos `*Check` são as garantias de segurança consolidadas pelo backend. Em uma captura legítima, todos devem ser `true`.
</Info>

<Warning>
  `Success` não implica necessariamente liveness aprovado. Verifique sempre `livenessCheck == true` para confirmar aprovação biométrica.
</Warning>

## Exemplos de retorno

### Sucesso — liveness aprovado

O fluxo terminou e a verificação biométrica passou. `status` vem `"completed"`, `result` é `"live"` e todos os `*Check` são `true`.

```kotlin theme={"theme":"catppuccin-latte"}
LivenessFacetecSDKLivenessState.Success(
    data = LivenessFacetecSDKResultData(
        sessionId = "8f3a9c10-2b7d-4e1f-9c2a-5d6e7f8a9b01",
        captureId = "1c4e5f60-3a2b-4d8e-9f10-7b6c5d4e3f21",
        status = "completed",
        processedAt = "2026-04-28T14:32:18.421Z",
        result = "live",
        scanResultBlob = "...",
        transactionId = "txn_abc123def456",
        retryScreen = null,
        replayCheck = true,
        sessionTokenCheck = true,
        auditTrailCheck = true,
        livenessCheck = true
    )
)
```

### Sucesso — liveness reprovado

O fluxo terminou tecnicamente, mas o resultado biométrico foi negativo. `status` vem `"failed"`, `result` é `"not_live"` e `livenessCheck` é `false`. O backend pode também marcar `auditTrailCheck` como `false` quando a trilha de auditoria não atende aos requisitos.

```kotlin theme={"theme":"catppuccin-latte"}
LivenessFacetecSDKLivenessState.Success(
    data = LivenessFacetecSDKResultData(
        sessionId = "8f3a9c10-2b7d-4e1f-9c2a-5d6e7f8a9b01",
        captureId = "9d8c7b6a-5e4f-3d2c-1b0a-9f8e7d6c5b4a",
        status = "failed",
        processedAt = "2026-04-28T14:35:02.108Z",
        result = "not_live",
        scanResultBlob = "...",
        transactionId = "txn_xyz789ghi012",
        retryScreen = 2,
        replayCheck = true,
        sessionTokenCheck = true,
        auditTrailCheck = false,
        livenessCheck = false
    )
)
```

<Info>
  Esse caso **não é erro técnico** — o SDK funcionou corretamente, mas a prova de vida foi reprovada. Trate como negativa de verificação no seu app (mostrar mensagem ao usuário e permitir nova tentativa), sem reportar como falha do SDK.
</Info>

### Erro do fluxo

Quando algo no fluxo técnico falha, o SDK emite `Error(error: LivenessFacetecSDKError)`. As subclasses possíveis cobrem rede, captura, submissão, cancelamento e exceções não mapeadas:

```kotlin theme={"theme":"catppuccin-latte"}
// Sem conectividade ou timeout HTTP
LivenessFacetecSDKLivenessState.Error(
    error = LivenessFacetecSDKError.NetworkError("Connection timed out")
)

// Falha durante a captura biométrica
LivenessFacetecSDKLivenessState.Error(
    error = LivenessFacetecSDKError.CaptureError("FaceTec session timed out")
)

// Falha ao submeter a captura para o backend
LivenessFacetecSDKLivenessState.Error(
    error = LivenessFacetecSDKError.SubmitError("HTTP 500")
)

// Usuário fechou a UI do FaceTec
LivenessFacetecSDKLivenessState.Error(
    error = LivenessFacetecSDKError.Cancelled()
)
```

A hierarquia completa de erros, com causa provável e ação recomendada para cada subclasse, está em [Solução de problemas](/plataforma/liveness/facetec/sdk/android/troubleshooting).

## Exemplo completo

Esse exemplo é uma adaptação do app de demonstração que acompanha o SDK:

```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.LivenessFacetecSDKConfig
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 btnInit = findViewById<Button>(R.id.btn_init)
        val btnStart = findViewById<Button>(R.id.btn_start)
        val progress = findViewById<ProgressBar>(R.id.progress)
        val tvResult = findViewById<TextView>(R.id.tv_result)

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

            lifecycleScope.launch {
                progress.visibility = View.VISIBLE
                LivenessFacetecSDK.init(
                    applicationContext = applicationContext,
                    config = LivenessFacetecSDKConfig()
                ).onFailure { e ->
                    Log.e("MainActivity", e.message.orEmpty())
                    progress.visibility = View.GONE
                    tvResult.text = "Falha ao iniciar o SDK"
                }.onSuccess { initData ->
                    // Criar sessão no backend com visitorId e requestId
                    val session = mySessionApi.createSession(
                        body = SessionRequest(
                            visitorId = initData.visitorId.orEmpty(),
                            eventId   = initData.requestId.orEmpty()
                        )
                    )
                    progress.visibility = View.GONE
                    btnInit.isEnabled = false
                    btnStart.tag = session
                    btnStart.isEnabled = true
                    tvResult.text = "SDK pronto"
                }
            }
        }

        btnStart.setOnClickListener {
            val session = btnStart.tag as? SessionResponse ?: return@setOnClickListener
            btnStart.isEnabled = false
            tvResult.text = ""

            lifecycleScope.launch {
                LivenessFacetecSDK.startLivenessCheck(
                    activity            = this@MainActivity,
                    sessionId           = session.sessionId,
                    sessionToken        = session.sessionToken,
                    facetecSessionToken = session.facetecSessionToken
                )
                    .catch { e ->
                        Log.e("MainActivity", e.message.orEmpty())
                        tvResult.text = "Erro: ${e.message}"
                        btnStart.isEnabled = true
                        progress.visibility = View.GONE
                    }
                    .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("Sucesso!")
                                    appendLine("Session: ${state.data.sessionId}")
                                    appendLine("Capture: ${state.data.captureId}")
                                    appendLine("Status: ${state.data.status}")
                                    appendLine("Liveness: ${state.data.livenessCheck}")
                                }
                                btnStart.isEnabled = true
                            }
                            is LivenessFacetecSDKLivenessState.Error -> {
                                Log.e("MainActivity", state.error.message)
                                progress.visibility = View.GONE
                                tvResult.text = "Erro: ${state.error.message}"
                                btnStart.isEnabled = true
                            }
                        }
                    }
            }
        }
    }
}
```

## Parando o SDK

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

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

| Situação                           | Ação                                 |
| ---------------------------------- | ------------------------------------ |
| Teardown da Activity (`onDestroy`) | Chame `stop()` para liberar recursos |
| Entre capturas                     | Opcional — `init()` é idempotente    |

<Info>
  Se `stop()` for chamado durante uma captura em andamento, ela é cancelada imediatamente e o Flow emite `Error(Cancelled)`. Chamadas antes de `init()` ou duas vezes seguidas não causam crash.
</Info>

## Próximos passos

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

  <Card title="Solução de problemas" icon="wrench" href="/plataforma/liveness/facetec/sdk/android/troubleshooting">
    Hierarquia de erros e dicas de diagnóstico
  </Card>
</CardGroup>
