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

# Integração com Apps Nativos (React Native / Flutter)

> Use o widget KYC em apps mobile via fluxo redirect com deep link — sem WebView.

## Por que não WebView?

O widget KYC usa `getUserMedia` para acessar a câmera em tempo real (selfie + liveness). Esse recurso é bloqueado ou instável dentro de WebViews no iOS. A solução recomendada pelo mercado (Stripe, Onfido, Plaid) é o **fluxo redirect**: o app abre o widget em um browser externo e recebe o resultado via deep link.

***

## Fluxo Completo

```
App nativo
  │
  ├─ 1. App chama seu backend
  │       Seu backend → POST /v1/verify/session com redirect_uri
  │       Retorna: { session_token, widget_url }
  │
  ├─ 2. App abre widget_url no browser externo
  │
  │      Browser externo
  │        ├─ Widget roda normalmente (câmera nativa do browser)
  │        ├─ Consentimento → documento → selfie → liveness
  │        └─ Ao concluir → redireciona para redirect_uri
  │
  └─ 3. App recebe deep link:
         meuapp://kyc-complete?verification_id=uuid&status=processing
         └─ Resultado final chega via webhook ou polling
```

***

## 1. Criar sessão com `redirect_uri`

No **backend** do cliente, inclua o campo `redirect_uri` ao criar a sessão:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api-kyc.campeaosec.com/v1/verify/session \
    -H "Authorization: Bearer sk_test_sua_chave" \
    -H "Content-Type: application/json" \
    -d '{
      "country": "BR",
      "external_id": "user-123",
      "ambiente": "sandbox",
      "redirect_uri": "meuapp://kyc-complete"
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch('https://api-kyc.campeaosec.com/v1/verify/session', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_test_sua_chave',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      country: 'BR',
      external_id: 'user-123',
      ambiente: 'sandbox',
      redirect_uri: 'meuapp://kyc-complete',
    }),
  })
  const { session_token, widget_url } = await res.json()
  // Retorne widget_url para o app (nunca exponha a API key)
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api-kyc.campeaosec.com/v1/verify/session',
      headers={'Authorization': 'Bearer sk_test_sua_chave'},
      json={
          'country': 'BR',
          'external_id': 'user-123',
          'ambiente': 'sandbox',
          'redirect_uri': 'meuapp://kyc-complete',
      }
  )
  data = response.json()
  widget_url = data['widget_url']
  ```
</CodeGroup>

**Resposta quando `redirect_uri` está presente:**

```json theme={null}
{
  "session_token": "a3f8c2d1b4e9...",
  "widget_url": "https://api-kyc.campeaosec.com/widget?token=a3f8c2d1b4e9...",
  "expira_em": "2026-07-31T15:30:00Z"
}
```

O `widget_url` já contém o token. Passe-o para o app — o app nunca precisa conhecer a API Key nem o `session_token`.

<Warning>
  `redirect_uri` só aceita **deep links** (ex: `meuapp://`, `com.empresa.app://`). URLs com `http://` ou `https://` são rejeitadas por segurança.
</Warning>

***

## 2. Abrir o widget no app

### React Native (Expo)

```typescript theme={null}
import * as WebBrowser from 'expo-web-browser'

// widgetUrl vem do seu backend
const result = await WebBrowser.openAuthSessionAsync(
  widgetUrl,
  'meuapp://kyc-complete'  // mesmo scheme do redirect_uri
)

if (result.type === 'success') {
  const url = new URL(result.url)
  const verificationId = url.searchParams.get('verification_id')
  const status = url.searchParams.get('status') // sempre "processing"

  // Aguarde o webhook ou faça polling
  await pollOrWaitWebhook(verificationId)
}
```

Instale a dependência:

```bash theme={null}
npx expo install expo-web-browser
```

### React Native (bare / CLI)

```typescript theme={null}
import { Linking } from 'react-native'
import InAppBrowser from 'react-native-inappbrowser-reborn'

if (await InAppBrowser.isAvailable()) {
  const result = await InAppBrowser.openAuth(
    widgetUrl,
    'meuapp://kyc-complete',
    { ephemeralWebSession: false }
  )

  if (result.type === 'success') {
    const url = new URL(result.url)
    const verificationId = url.searchParams.get('verification_id')
  }
}
```

### Flutter

```dart theme={null}
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';

final result = await FlutterWebAuth2.authenticate(
  url: widgetUrl,
  callbackUrlScheme: 'meuapp',
);

final uri = Uri.parse(result);
final verificationId = uri.queryParameters['verification_id'];
```

***

## 3. Configurar deep link no app

### Expo (`app.json`)

```json theme={null}
{
  "expo": {
    "scheme": "meuapp"
  }
}
```

### Android (`AndroidManifest.xml` — bare workflow)

```xml theme={null}
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="meuapp" android:host="kyc-complete" />
</intent-filter>
```

### iOS (`Info.plist`)

```xml theme={null}
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>meuapp</string>
    </array>
  </dict>
</array>
```

***

## 4. Deep link recebido

```
meuapp://kyc-complete?verification_id=550e8400-e29b-41d4-a716-446655440000&status=processing
```

| Parâmetro         | Valor        | Descrição                                               |
| ----------------- | ------------ | ------------------------------------------------------- |
| `verification_id` | UUID         | ID da verificação para consultar ou receber via webhook |
| `status`          | `processing` | Sempre `processing` — análise ainda em curso no backend |

O resultado final (`approved` / `rejected` / `manual_review`) chega via:

* **Webhook** configurado na sessão (recomendado) — ver [Webhook](/kyc/webhook)
* **Polling** em `GET /v1/verify/{verification_id}` com sua API Key

<Note>
  O `session_token` **nunca** aparece no deep link. O deep link só contém o `verification_id` (UUID público), sem scores, OCR nem dados internos.
</Note>

***

## Segurança

| Ponto           | Proteção                                                            |
| --------------- | ------------------------------------------------------------------- |
| API Key         | Nunca vai ao app — fica no seu backend                              |
| `session_token` | Nunca aparece no deep link de retorno                               |
| `redirect_uri`  | Só aceita deep links (não `http://`)                                |
| Scores e OCR    | Nunca no deep link — apenas via API autenticada ou webhook assinado |

***

## Vantagens do fluxo redirect

* Câmera nativa do browser — funciona em iOS e Android sem workarounds de WebView
* Widget atualiza sem o cliente publicar nova versão do app
* A mesma URL funciona para web e mobile
* Padrão já conhecido por desenvolvedores (mesmo fluxo do Sign in with Google, Stripe Checkout, Plaid Link)
