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

# WebView Integration

> Integrate Didit identity verification via WebView in iOS, Android, React Native, and Flutter apps. Camera permissions handled. Pay-per-call from $0.30.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="WebView Integration Prompt"
  prompt={`Integrate Didit identity verification into my mobile app using a WebView.

## IMPORTANT: Prefer Native SDKs
Native SDKs exist for every major mobile platform: iOS, Android, React Native, and Flutter.
They provide better UX, NFC passport/ID reading, optimized camera, and biometric integration.
Only use a WebView if you are on a platform with no native SDK (e.g., Xamarin, Cordova).
- iOS native SDK: https://docs.didit.me/integration/native-sdks/ios-sdk
- Android native SDK: https://docs.didit.me/integration/native-sdks/android-sdk
- React Native SDK: https://docs.didit.me/integration/native-sdks/react-native-sdk
- Flutter SDK: https://docs.didit.me/integration/native-sdks/flutter-sdk

## Architecture
1. Backend creates a session via POST /v3/session/ → response includes "url"
2. App loads response.url in a WebView
3. User completes verification inside the WebView
4. WebView navigates to your callback URL when done — app intercepts this
5. Backend receives the full, signed decision via webhook (status.updated)

## React Native (Expo / CLI)
Install: npx expo install react-native-webview

import { WebView } from 'react-native-webview';

function VerificationScreen({ verificationUrl, callbackUrl, onDone }) {
const handleNavigationChange = (navState) => {
if (navState.url.startsWith(callbackUrl)) {
  const u = new URL(navState.url);
  onDone({
    sessionId: u.searchParams.get('verificationSessionId'),
    status:    u.searchParams.get('status'), // "Approved" | "Declined" | "In Review"
  });
}
};

return (
<WebView
  source={{ uri: verificationUrl }}
  onNavigationStateChange={handleNavigationChange}
  javaScriptEnabled
  domStorageEnabled
  mediaPlaybackRequiresUserAction={false}
  allowsInlineMediaPlayback
  mediaCapturePermissionGrantType="grant"
/>
);
}

## Flutter
Install: flutter pub add webview_flutter webview_flutter_android webview_flutter_wkwebview

controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate(
onNavigationRequest: (request) {
  if (request.url.startsWith(callbackUrl)) {
    final uri = Uri.parse(request.url);
    Navigator.pop(context, {
      'sessionId': uri.queryParameters['verificationSessionId'],
      'status':    uri.queryParameters['status'],
    });
    return NavigationDecision.prevent;
  }
  return NavigationDecision.navigate;
},
))
..loadRequest(Uri.parse(verificationUrl));

## Backend: Create Session
POST https://verification.didit.me/v3/session/
Headers: { "x-api-key": DIDIT_API_KEY, "Content-Type": "application/json" }
Body: {
"workflow_id": DIDIT_WORKFLOW_ID,
"callback": "https://myapp.com/verification-done", // or a custom scheme like myapp://done
"callback_method": "both",
"vendor_data": "user-id"
}
201 response (all 10 fields, per OpenAPI): { session_id, session_number, session_token, url,
vendor_data, metadata, status, workflow_id, workflow_version, callback }
The url embeds the 12-char session_token, e.g. https://verify.didit.me/session/3FaJ9wLqX2Mz
Load response.url in the WebView — never build the URL from session_token by hand.

## Important WebView Settings
- Enable JavaScript and DOM storage
- Grant camera/microphone permissions automatically
- Allow inline media playback (no fullscreen takeover on iOS)
- Set a generic mobile user agent string
- Intercept navigation to your callback URL to detect completion

## Rate Limits
600 session-create requests per minute per API key (falls back to bearer token, then client IP), plus a shared 300 write-requests/minute budget across all endpoints. HTTP 429 with Retry-After when exceeded.

## Docs
- WebView guide: https://docs.didit.me/integration/web-sdks/webview-in-ios-android
- iOS SDK: https://docs.didit.me/integration/native-sdks/ios-sdk
- Android SDK: https://docs.didit.me/integration/native-sdks/android-sdk
- Webhooks: https://docs.didit.me/integration/webhooks
`}
/>

This guide covers how to embed the **Didit web verification flow** inside a native mobile **WebView** component. It is **not** the same as the native iOS/Android/React Native/Flutter SDKs — it is a fallback for platforms that cannot use a native SDK.

<Warning>
  ### Always prefer native SDKs

  Native SDKs are available for **iOS**, **Android**, **React Native**, and **Flutter**. They provide significantly better performance, camera handling, NFC passport/ID reading, biometric integration, and user experience. **Use WebView only if no native SDK is available for your platform.**

  <CardGroup cols={2}>
    <Card title="iOS SDK" icon="apple" href="/integration/native-sdks/ios-sdk">
      Native Swift SDK for iOS 13+
    </Card>

    <Card title="Android SDK" icon="android" href="/integration/native-sdks/android-sdk">
      Native Kotlin SDK for Android 6.0+
    </Card>

    <Card title="React Native SDK" icon="react" href="/integration/native-sdks/react-native-sdk">
      Cross-platform TypeScript API. Expo compatible.
    </Card>

    <Card title="Flutter SDK" icon="feather" href="/integration/native-sdks/flutter-sdk">
      Cross-platform Dart API. pub.dev package.
    </Card>
  </CardGroup>
</Warning>

| Platform        | Preferred Integration                                                        | WebView                 |
| --------------- | ---------------------------------------------------------------------------- | ----------------------- |
| iOS             | [iOS Native SDK](/integration/native-sdks/ios-sdk) — **Use this**            | Not recommended         |
| Android         | [Android Native SDK](/integration/native-sdks/android-sdk) — **Use this**    | Not recommended         |
| React Native    | [React Native SDK](/integration/native-sdks/react-native-sdk) — **Use this** | Not recommended         |
| Flutter         | [Flutter SDK](/integration/native-sdks/flutter-sdk) — **Use this**           | Not recommended         |
| Other platforms | N/A                                                                          | Use WebView as fallback |

***

## When to Use WebView

Use WebView integration **only** when:

* You're building on a platform without a native SDK (e.g., Xamarin, Cordova, or other niche frameworks)
* You need a quick prototype or proof of concept before integrating the native SDK
* You're migrating from web and want a temporary bridge to a native integration

***

## How It Works

<Steps>
  <Step title="Create a session">
    Your backend calls the [Create Session API](/sessions-api/create-session) and receives a hosted `url`.
  </Step>

  <Step title="Open in WebView">
    Your app loads the `url` in a WebView component.
  </Step>

  <Step title="User completes verification">
    The user goes through the verification flow inside the WebView.
  </Step>

  <Step title="Intercept callback">
    When done, the WebView navigates to your callback URL. Your app intercepts this navigation and extracts the result.
  </Step>

  <Step title="Get full results via webhook">
    Your backend receives the complete decision data via [webhook](/integration/webhooks).
  </Step>
</Steps>

***

## Creating a Session

Before opening the verification flow, create a session from your backend:

```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow_id": "your-workflow-id",
    "vendor_data": "user-123",
    "callback": "https://yourapp.com/callback"
  }'
```

Response (`201` — the `url` embeds the 12-character `session_token`, not the `session_id`):

```json theme={null}
{
  "session_id": "11111111-2222-3333-4444-555555555555",
  "session_number": 43762,
  "session_token": "3FaJ9wLqX2Mz",
  "url": "https://verify.didit.me/session/3FaJ9wLqX2Mz",
  "vendor_data": "user-123",
  "metadata": null,
  "status": "Not Started",
  "workflow_id": "11111111-2222-3333-4444-555555555555",
  "workflow_version": 3,
  "callback": "https://yourapp.com/callback"
}
```

Load the `url` field in the WebView — never build it from `session_token` by hand.

***

## React Native

Install the WebView package:

```bash theme={null}
npm install react-native-webview
# or with Expo
npx expo install react-native-webview
```

### Implementation

```javascript theme={null}
import React from 'react';
import { WebView } from 'react-native-webview';

const VerificationScreen = ({ sessionUrl, onComplete }) => {
  return (
    <WebView
      source={{ uri: sessionUrl }}
      // Required: generic mobile user agent
      userAgent="Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"
      // Required for camera/media
      mediaPlaybackRequiresUserAction={false}
      allowsInlineMediaPlayback={true}
      // Android-specific
      domStorageEnabled={true}
      // Performance
      androidHardwareAccelerationDisabled={false}
      androidLayerType="hardware"
      // Intercept callback
      onNavigationStateChange={(navState) => {
        if (navState.url.includes('yourapp.com/callback')) {
          const url = new URL(navState.url);
          const sessionId = url.searchParams.get('verificationSessionId');
          const status = url.searchParams.get('status');
          onComplete({ sessionId, status });
        }
      }}
    />
  );
};

export default VerificationScreen;
```

<Card title="React Native Demo" icon="github" href="https://github.com/didit-protocol/expo-didit-verification-webview">
  Expo + WebView verification example
</Card>

***

## Flutter

Add the required packages to `pubspec.yaml`:

```yaml theme={null}
dependencies:
  webview_flutter: ^4.4.0
  webview_flutter_wkwebview: ^3.9.0
  webview_flutter_android: ^3.12.0
```

### Implementation

```dart theme={null}
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';

class VerificationScreen extends StatefulWidget {
  final String sessionUrl;
  
  const VerificationScreen({super.key, required this.sessionUrl});

  @override
  State<VerificationScreen> createState() => _VerificationScreenState();
}

class _VerificationScreenState extends State<VerificationScreen> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _setupWebView();
  }

  void _setupWebView() {
    late final PlatformWebViewControllerCreationParams params;
    
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const {},
      );
    } else {
      params = const PlatformWebViewControllerCreationParams();
    }

    _controller = WebViewController.fromPlatformCreationParams(params)
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setUserAgent(
        'Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 '
        '(KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36'
      )
      ..setNavigationDelegate(
        NavigationDelegate(
          onNavigationRequest: (NavigationRequest request) {
            if (request.url.contains('yourapp.com/callback')) {
              _handleResult(request.url);
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse(widget.sessionUrl));

    // Grant camera/microphone permissions
    final platform = _controller.platform;
    platform.setOnPlatformPermissionRequest((request) {
      request.grant();
    });

    // Android-specific
    if (platform is AndroidWebViewController) {
      platform.setMediaPlaybackRequiresUserGesture(false);
    }
  }

  void _handleResult(String callbackUrl) {
    final uri = Uri.parse(callbackUrl);
    final sessionId = uri.queryParameters['verificationSessionId'];
    final status = uri.queryParameters['status'];
    
    Navigator.of(context).pop({
      'sessionId': sessionId,
      'status': status,
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Identity Verification')),
      body: WebViewWidget(controller: _controller),
    );
  }
}
```

<Card title="Flutter Demo" icon="github" href="https://github.com/didit-protocol/flutter-didit-verification-webview">
  Flutter WebView verification example
</Card>

***

## Critical Configuration

### User Agent (Required)

You **must** set a generic mobile user agent for the WebView to work correctly:

```
Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36
```

This ensures proper camera access, correct UI rendering, and optimal media playback.

### Media Settings (Required)

| Setting                            | Purpose                          |
| ---------------------------------- | -------------------------------- |
| `allowsInlineMediaPlayback`        | Prevents fullscreen video on iOS |
| `mediaPlaybackRequiresUserGesture` | Allows auto-starting the camera  |
| `javaScriptEnabled`                | Required for SDK functionality   |
| `domStorageEnabled`                | Session storage for Android      |

***

## Handling the Callback

After verification, the WebView redirects to your callback URL with query parameters:

```
https://yourapp.com/callback?verificationSessionId=abc123&status=Approved
```

| Parameter               | Description                            |
| ----------------------- | -------------------------------------- |
| `verificationSessionId` | Unique session identifier              |
| `status`                | `Approved`, `Declined`, or `In Review` |

<Tip>
  For a better user experience, use a **custom URL scheme** (e.g., `myapp://verification-complete`) as your callback and handle it as a deep link in your app.
</Tip>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Camera not working">
    1. Ensure `mediaPlaybackRequiresUserGesture` is `false`
    2. Verify camera permissions are granted at the OS level
    3. Check that the user agent string is set correctly
  </Accordion>

  <Accordion title="White screen or loading issues">
    1. Verify the session URL is valid and not expired
    2. Check internet connectivity
    3. Ensure JavaScript is enabled in the WebView settings
  </Accordion>

  <Accordion title="Callback not triggering">
    1. Verify your callback URL matches what you set when creating the session
    2. Check that the navigation delegate/listener is properly configured
    3. Try setting `callback_method` to `"both"` when creating the session
  </Accordion>
</AccordionGroup>

***
