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

    public func getSessionData() async -> Result<LivenessFacetecSDKSessionData, LivenessFacetecSDKError>

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

    public func stop()
}
```

* `initialize` é **assíncrona** e **não retorna valor**. Lança `LivenessFacetecSDKInitError` em caso de falha. Verifique `isInitialized` antes de chamar — para reinicializar (ex.: logout com limpeza de estado), chame `stop()` antes de um novo `initialize()`.
* `getSessionData` coleta o `ssid` de perimeter security, usado pelo seu backend na criação da sessão.
* `startLivenessCheck` requer uma `UIViewController` e os dois tokens de sessão criados pelo consumidor.
* `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.

<Info>
  **Threading:** `LivenessFacetecSDKClient` é isolado ao `@MainActor`. Tanto `initialize(config:)` quanto `startLivenessCheck(...)` devem ser chamados a partir do main actor — um `Task { }` criado dentro de um método `@MainActor` (como callbacks de `UIViewController` ou do `AppDelegate`) herda automaticamente o contexto correto.
</Info>

<Info>
  As chaves internas do SDK são compiladas no binário. **Nenhuma chave precisa ser fornecida pelo app consumidor** — não existe parâmetro de API key em `LivenessFacetecSDKConfig`.
</Info>

<Info>
  **Chame `initialize` cedo.** O preparo interno de chaves roda na main thread a cada launch e custa de dezenas a poucas centenas de milissegundos, dependendo do dispositivo. Iniciando o SDK no começo do ciclo de vida do app, esse custo não recai sobre a primeira tela interativa.
</Info>

## 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/v10/sdk/ios/customizacao) |

## Dados de sessão

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

```swift theme={"theme":"catppuccin-latte"}
public struct LivenessFacetecSDKSessionData {
    public let ssid: String
}
```

| Campo  | Tipo     | Descrição                                                   |
| ------ | -------- | ----------------------------------------------------------- |
| `ssid` | `String` | Identificador de sessão de dispositivo. Pode vir **vazio**. |

<Info>
  `getSessionData()` **não lança** — devolve um `Result`. A coleta é **best-effort**: qualquer falha interna devolve `.success` com `ssid` vazio, nunca `.failure`. O único caso de `.failure` é o SDK não ter sido inicializado, que chega como `.unknown`.
</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 `initialize()`, o consumidor 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 consumidor 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 `AsyncStream`

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

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

O stream encerra após `.success` ou `.error` — o loop `for await` termina naturalmente.

<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 `AsyncStream` entrega apenas o desfecho final da captura.

  **Retentativa depois do desfecho:** após um `.error`, é seguro chamar `startLivenessCheck` novamente, com os mesmos tokens (se ainda válidos) ou com tokens de uma sessão nova. Não há estado interno que precise ser reiniciado — apenas `initialize` precisa ter sido executado com sucesso anteriormente.
</Info>

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

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

    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {

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

            Task {
                do {
                    let config = LivenessFacetecSDKConfig()
                    try await LivenessFacetecSDKClient.shared.initialize(config: config)
                } catch let error as LivenessFacetecSDKInitError {
                    switch error {
                    case .alreadyInitialized:
                        break // já inicializado — chame stop() antes se precisar reinicializar
                    case .facetecInitializationFailed(let msg):
                        print("FaceTec init failed: \(msg)")
                    }
                } catch {
                    print("SDK init unexpected error: \(error)")
                }
            }

            return true
        }
    }
    ```
  </Step>

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

    ```swift theme={"theme":"catppuccin-latte"}
    switch await LivenessFacetecSDKClient.shared.getSessionData() {
    case .success(let sessionData):
        // sessionData.ssid pode vir vazio (coleta best-effort) — não trate como erro
        let tokens = try await criarSessao(ssid: sessionData.ssid)
        // tokens: sessionId, sessionToken
    case .failure(let error):
        // SDK não inicializado. Chame initialize(config:) antes de getSessionData().
        print("Erro ao coletar dados da sessão: \(error.localizedDescription)")
    }
    ```
  </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) {
            Task {
                let stream = LivenessFacetecSDKClient.shared.startLivenessCheck(
                    viewController: self,
                    sessionId: sessionId,
                    sessionToken: sessionToken
                )

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

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

                    case .error(let error):
                        tratarErro(error)
                    }
                }
            }
        }
    }
    ```

    <Warning>
      Chamar `startLivenessCheck` sem ter executado `initialize(config:)` com sucesso não causa crash: o stream emite imediatamente `.error(.unknown("SDK not initialized. Call initialize() first."))` e encerra.
    </Warning>
  </Step>

  <Step title="Tratar o resultado">
    No caso de sucesso, reconcilie a captura no backend usando o `sessionId` que você já tem da criação de sessão. No caso de erro, mapeie cada caso de `LivenessFacetecSDKError` para uma mensagem ou ação adequada — veja [Solução de problemas](/plataforma/liveness/facetec/v10/sdk/ios/troubleshooting).
  </Step>
</Steps>

## Campos de `LivenessFacetecSDKResultData`

```swift theme={"theme":"catppuccin-latte"}
public struct LivenessFacetecSDKResultData {
    public let ageGroup: Int?
    public let auditTrailImage: String?
    public let status: ClientStatus
    public let message: String
}
```

| Campo             | Tipo           | Descrição                                                             |
| ----------------- | -------------- | --------------------------------------------------------------------- |
| `status`          | `ClientStatus` | Status final da sessão FaceTec. Sempre `.sessionCompleted` neste caso |
| `message`         | `String`       | Mensagem legível correspondente ao `status`                           |
| `ageGroup`        | `Int?`         | Faixa etária estimada, quando disponível                              |
| `auditTrailImage` | `String?`      | Imagem de auditoria da captura em base64, quando disponível           |

<Info>
  `.success` é emitido **sem depender de nenhuma chamada de rede adicional** — o resultado vem direto do fim da sessão FaceTec. Qualquer outro desfecho (cancelamento pelo usuário, lockout, erro de câmera etc.) chega como `.error(.sessionNotCompleted(status:message:))`.
</Info>

<Info>
  O `LivenessFacetecSDKResultData` **não** traz o `sessionId` — use o valor que você já passou para `startLivenessCheck` para correlacionar a captura no seu backend.
</Info>

<Warning>
  O enum `ClientStatus` declara **13 casos**, mas o iOS produz apenas **8** deles. Os outros 5 existem por compatibilidade com o vocabulário de status compartilhado com a Web e nunca são emitidos aqui — mas o compilador continua exigindo os 13 num `switch` exaustivo. Se preferir, use um `default` para os casos não emitidos. Veja a lista em [Solução de problemas](/plataforma/liveness/facetec/v10/sdk/ios/troubleshooting#status-possiveis-em-sessionnotcompleted).
</Warning>

## Exemplos de retorno

### Sucesso — liveness aprovado

```swift theme={"theme":"catppuccin-latte"}
.success(
    LivenessFacetecSDKResultData(
        ageGroup: 30,
        auditTrailImage: "...",
        status: .sessionCompleted,
        message: "The Session was performed successfully."
    )
)
```

### Falha — usuário cancelou a captura

```swift theme={"theme":"catppuccin-latte"}
.error(
    .sessionNotCompleted(
        status: .userCancelledFaceScan,
        message: "The user cancelled before performing enough Scans to Succeed."
    )
)
```

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

## Parar e reinicializar o SDK

`stop()` libera todo o estado interno do SDK e zera as chaves sensíveis em memória. Após `stop()`, `initialize()` pode ser chamado novamente com comportamento idêntico ao primeiro uso.

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

// Teardown — garanta que nenhuma captura está em progresso
LivenessFacetecSDKClient.shared.stop()
print(LivenessFacetecSDKClient.shared.isInitialized) // false

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

Casos de uso típicos: logout com limpeza de dados sensíveis e testes instrumentados que precisam isolar estado entre casos.

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

<Info>
  **Não** use `stop()`/`initialize()` para trocar de ambiente. O host de API é compilado no binário — um mesmo artefato nunca muda de ambiente em runtime.
</Info>

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

    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. Coletar o ssid e criar a sessão no backend
            guard case .success(let sessionData) =
                    await LivenessFacetecSDKClient.shared.getSessionData(),
                  let tokens = try? await criarSessao(ssid: sessionData.ssid)
            else {
                isLoading = false
                resultMessage = "Erro ao criar sessão."
                return
            }

            // 2. Iniciar a captura com os tokens da sessão
            let stream = LivenessFacetecSDKClient.shared.startLivenessCheck(
                viewController: viewController,
                sessionId: tokens.sessionId,
                sessionToken: tokens.sessionToken
            )

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

                case .success(let data):
                    isLoading = false
                    resultMessage = """
                    Liveness finalizado
                    \(data.message)
                    Age group: \(data.ageGroup.map(String.init) ?? "n/d")
                    """

                case .error(let error):
                    isLoading = false
                    if case .sessionNotCompleted(_, let message) = error {
                        resultMessage = message
                    } else {
                        resultMessage = error.localizedDescription
                    }
                }
            }
        }
    }
}
```

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

| Área                 | v9                                                                                                                | v10                                                                                                                                                            |
| -------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `initialize`         | Retornava `LivenessFacetecSDKInitResult` com dados de fingerprint                                                 | **Não retorna valor**                                                                                                                                          |
| Device signal        | `fingerprintVisitorId`/`fingerprintRequestId` (FingerprintJS)                                                     | `getSessionData()` devolve `ssid` (perimeter security)                                                                                                         |
| `startLivenessCheck` | `(viewController, sessionId, sessionToken, facetecSessionToken)`                                                  | `(viewController, sessionId, sessionToken)` — não existe mais `facetecSessionToken`                                                                            |
| Resultado            | 12 campos (`sessionId`, `captureId`, `status: String`, `result`, `scanResultBlob`, `livenessCheck`, entre outros) | 4 campos: `status` (enum `ClientStatus`), `message`, `ageGroup?`, `auditTrailImage?`                                                                           |
| Veredito             | `.success` sempre; conferir `livenessCheck == true` para saber se passou                                          | `.success` só quando a sessão completa; qualquer outro desfecho chega como `.error`                                                                            |
| Erros                | `captureError`, `submitError`, `networkError`, `cancelled`, `unknown`                                             | `cancelled` foi removido; entrou `sessionNotCompleted(status:message:)`. Veja [Solução de problemas](/plataforma/liveness/facetec/v10/sdk/ios/troubleshooting) |
| Frameworks embutidos | `FaceTecSDK` + `FingerprintPro`                                                                                   | `FaceTecSDK` + `ShieldPtr`                                                                                                                                     |

<Warning>
  Se o seu código v9 lia `livenessCheck`, `captureId`, `scanResultBlob` ou tratava `.cancelled`, 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/ios/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/ios/troubleshooting">
    Hierarquia de erros e dicas de diagnóstico
  </Card>
</CardGroup>
