> ## 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 no iOS

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

## API pública

A fachada do SDK fica em `LivenessFacetecSDKClient.shared`:

```swift theme={"theme":"catppuccin-latte"}
@MainActor
public final class LivenessFacetecSDKClient {
    public static let shared: LivenessFacetecSDKClient

    public var isInitialized: Bool

    public func initialize(config: LivenessFacetecSDKConfig) async throws -> LivenessFacetecSDKInitResult

    public func startLivenessCheck(
        viewController: UIViewController,
        sessionId: String,
        sessionToken: String,
        facetecSessionToken: String
    ) -> AsyncStream<LivenessFacetecSDKLivenessState>

    public func stop()
}
```

* `initialize` é **assíncrona**, retorna `LivenessFacetecSDKInitResult` com dados de fingerprint. Verifique `isInitialized` antes de chamar — para reinicializar (ex.: troca de ambiente ou logout), chame `stop()` antes de um novo `initialize()`.
* `startLivenessCheck` requer uma `UIViewController` e os três tokens de sessão criados pelo consumidor via `POST /api/v1/sessions`.
* `stop()` libera o estado interno do SDK. Não chamar enquanto uma captura estiver em progresso.
* `isInitialized` indica se `initialize` foi concluído com sucesso.

## Configuração

```swift theme={"theme":"catppuccin-latte"}
public struct LivenessFacetecSDKConfig {
    public let customization: LivenessFacetecSDKCustomization?

    public init(customization: LivenessFacetecSDKCustomization? = nil)
}
```

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

## Estados do `AsyncStream`

`startLivenessCheck` retorna um `AsyncStream<LivenessFacetecSDKLivenessState>` que emite um único caminho `loading → success` ou `loading → error` antes de completar:

```swift theme={"theme":"catppuccin-latte"}
public enum LivenessFacetecSDKLivenessState {
    case loading
    case success(LivenessFacetecSDKResultData)
    case error(LivenessFacetecSDKError)
}
```

## Fluxo de uso

<Steps>
  <Step title="Inicializar o SDK">
    Chame `LivenessFacetecSDKClient.shared.initialize` uma única vez no início do ciclo de vida do app (preferencialmente no `AppDelegate`). O método retorna `LivenessFacetecSDKInitResult` com os dados de fingerprint — armazene-os para usá-los ao criar a sessão:

    ```swift theme={"theme":"catppuccin-latte"}
    import LivenessFacetecSDK
    import UIKit

    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {

        var initResult: LivenessFacetecSDKInitResult?

        func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {

            Task {
                do {
                    let config = LivenessFacetecSDKConfig()
                    self.initResult = try await LivenessFacetecSDKClient.shared.initialize(config: config)
                } catch let error as LivenessFacetecSDKInitError {
                    switch error {
                    case .alreadyInitialized:
                        break
                    case .provisioningFailed(let msg):
                        print("Provisioning failed: \(msg)")
                    case .facetecInitializationFailed(let msg):
                        print("FaceTec init failed: \(msg)")
                    }
                }
            }

            return true
        }
    }
    ```
  </Step>

  <Step title="Criar sessão no backend">
    Antes de iniciar o liveness check, crie uma sessão com os dados de fingerprint do `LivenessFacetecSDKInitResult`
  </Step>

  <Step title="Iniciar a verificação">
    Passe os tokens de sessão obtidos no passo anterior:

    ```swift theme={"theme":"catppuccin-latte"}
    import LivenessFacetecSDK
    import UIKit

    final class VerificationViewController: UIViewController {

        func iniciarLiveness(sessionId: String, sessionToken: String, facetecSessionToken: String) {
            Task {
                let stream = LivenessFacetecSDKClient.shared.startLivenessCheck(
                    viewController: self,
                    sessionId: sessionId,
                    sessionToken: sessionToken,
                    facetecSessionToken: facetecSessionToken
                )

                for await state in stream {
                    await MainActor.run {
                        switch state {
                        case .loading:
                            mostrarProgresso()

                        case .success(let data):
                            tratarSucesso(data)

                        case .error(let error):
                            tratarErro(error)
                        }
                    }
                }
            }
        }
    }
    ```
  </Step>

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

## Parar e reinicializar o SDK

`stop()` libera o estado interno do SDK e permite uma nova chamada a `initialize()`. Use quando precisar trocar de ambiente, realizar logout com limpeza de estado, ou isolar estado entre testes.

```swift theme={"theme":"catppuccin-latte"}
// Verificar estado
print(LivenessFacetecSDKClient.shared.isInitialized) // true após initialize com sucesso

// Teardown — aguarde o stream de startLivenessCheck encerrar antes de chamar
LivenessFacetecSDKClient.shared.stop()
print(LivenessFacetecSDKClient.shared.isInitialized) // false

// Reinicialização — comporta-se como primeira vez
let result = try await LivenessFacetecSDKClient.shared.initialize(config: config)
```

<Warning>
  Não chame `stop()` enquanto uma captura estiver em progresso. Aguarde o stream de `startLivenessCheck` encerrar (`.success` ou `.error`) antes de chamar.
</Warning>

## Campos de `LivenessFacetecSDKResultData`

Quando a verificação termina com sucesso, o SDK emite `.success(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`       | `Bool`    | Resultado da verificação anti-replay            |
| `sessionTokenCheck` | `Bool`    | Validação do token de sessão                    |
| `auditTrailCheck`   | `Bool`    | Validação da trilha de auditoria                |
| `livenessCheck`     | `Bool`    | 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>

## Erros possíveis

### `LivenessFacetecSDKInitError`

Lançados por `initialize(config:)`:

| Caso                                  | Causa                                                                                                                                              |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `alreadyInitialized`                  | `initialize()` foi chamado enquanto o SDK já estava inicializado. Verifique `isInitialized` antes de chamar, ou chame `stop()` para reinicializar. |
| `provisioningFailed(String)`          | Falha nas chaves internas — blob corrompido ou chaves ausentes no build. A mensagem associada contém diagnóstico.                                  |
| `facetecInitializationFailed(String)` | FaceTec SDK rejeitou as chaves internas. A mensagem associada contém diagnóstico do FaceTec.                                                       |

### `LivenessFacetecSDKError`

Emitidos como `.error(LivenessFacetecSDKError)` pelo `AsyncStream`:

| Caso                   | Causa                                              |
| ---------------------- | -------------------------------------------------- |
| `captureError(String)` | Erro interno durante a captura FaceTec             |
| `submitError(String)`  | Falha ao enviar os dados de captura para o backend |
| `networkError(String)` | Erro de rede (timeout, sem conectividade)          |
| `cancelled`            | Usuário fechou a tela de captura                   |
| `unknown(String)`      | Erro inesperado                                    |

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

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

```swift theme={"theme":"catppuccin-latte"}
.success(
    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: nil,
        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`.

```swift theme={"theme":"catppuccin-latte"}
.success(
    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(LivenessFacetecSDKError)`:

```swift theme={"theme":"catppuccin-latte"}
// Sem conectividade ou timeout HTTP
.error(.networkError("Connection timed out"))

// Falha durante a captura biométrica
.error(.captureError("FaceTec session timed out"))

// Usuário fechou a UI do FaceTec
.error(.cancelled)
```

## Exemplo completo (SwiftUI)

```swift theme={"theme":"catppuccin-latte"}
import SwiftUI
import LivenessFacetecSDK

struct LivenessView: View {
    @State private var isLoading = false
    @State private var resultMessage: String?

    // initResult deve ser passado pelo nível superior após initialize()
    let initResult: LivenessFacetecSDKInitResult

    var body: some View {
        VStack(spacing: 24) {
            Button(action: iniciarLiveness) {
                HStack {
                    if isLoading {
                        ProgressView().tint(.white)
                    }
                    Text(isLoading ? "Verificando..." : "Iniciar Liveness")
                }
                .frame(maxWidth: .infinity)
                .padding()
                .background(Color.accentColor)
                .foregroundColor(.white)
                .cornerRadius(12)
            }
            .disabled(isLoading)

            if let message = resultMessage {
                Text(message).multilineTextAlignment(.center)
            }
        }
        .padding()
    }

    private func iniciarLiveness() {
        guard let viewController = UIApplication.shared
            .connectedScenes
            .compactMap({ ($0 as? UIWindowScene)?.keyWindow?.rootViewController })
            .first
        else { return }

        isLoading = true
        resultMessage = nil

        Task {
            // 1. Criar sessão no backend com os dados de fingerprint
            guard let tokens = try? await criarSessao(initResult: initResult) else {
                await MainActor.run {
                    isLoading = false
                    resultMessage = "Erro ao criar sessão."
                }
                return
            }

            // 2. Iniciar liveness com os tokens de sessão
            let stream = LivenessFacetecSDKClient.shared.startLivenessCheck(
                viewController: viewController,
                sessionId: tokens.sessionId,
                sessionToken: tokens.sessionToken,
                facetecSessionToken: tokens.facetecSessionToken
            )

            for await state in stream {
                await MainActor.run {
                    switch state {
                    case .loading:
                        resultMessage = "Processando..."

                    case .success(let data):
                        isLoading = false
                        resultMessage = """
                        Sucesso!
                        Session: \(data.sessionId)
                        Capture: \(data.captureId)
                        Status: \(data.status)
                        Liveness: \(data.livenessCheck)
                        """

                    case .error(let error):
                        isLoading = false
                        resultMessage = "Erro: \(error)"
                    }
                }
            }
        }
    }
}
```

## Próximos passos

<CardGroup cols={2}>
  <Card title="Customização" icon="palette" href="/plataforma/liveness/facetec/sdk/ios/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/ios/troubleshooting">
    Hierarquia de erros e dicas de diagnóstico
  </Card>
</CardGroup>
