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

# WebView

> Embed the deposit UI in native mobile apps

Load the Daimo payment UI inside a native WebView (iOS WKWebView, Android WebView, or React Native). No SDK installation required — just create a session and construct a URL.

## How it works

1. Create a session via the API
2. Construct the webview URL from the session ID and client secret
3. Load that URL in your native WebView
4. Listen for `postMessage` events to track payment progress

## Create a session

```bash theme={null}
curl -X POST https://api.daimo.com/v1/sessions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": {
      "type": "evm",
      "address": "0xYourAddress",
      "chainId": 8453,
      "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amountUnits": "10.00"
    },
    "display": {
      "title": "Deposit to Acme",
      "verb": "Deposit"
    }
  }'
```

Then construct the webview URL from the response:

```
https://daimo.com/webview?session={sessionId}&cs={clientSecret}
```

## Load the WebView

### iOS (Swift)

```swift theme={null}
import WebKit

let webView = WKWebView(frame: view.bounds)
view.addSubview(webView)

// Register message handler for payment events
let handler = PaymentMessageHandler()
webView.configuration.userContentController.add(handler, name: "daimoPay")

// Construct the URL from your session response
let urlString = "https://daimo.com/webview?session=\(sessionId)&cs=\(clientSecret)"
let url = URL(string: urlString)!
webView.load(URLRequest(url: url))

class PaymentMessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(
        _ controller: WKUserContentController,
        didReceive message: WKScriptMessage
    ) {
        guard let body = message.body as? [String: Any],
              let type = body["type"] as? String else { return }

        switch type {
        case "ready": print("Payment UI loaded")
        case "paymentStarted": print("Payment initiated")
        case "paymentCompleted": print("Payment succeeded")
        default: break
        }
    }
}
```

### Android (Kotlin)

```kotlin theme={null}
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true

webView.addJavascriptInterface(object {
    @JavascriptInterface
    fun postMessage(message: String) {
        val json = JSONObject(message)
        when (json.getString("type")) {
            "ready" -> Log.d("Daimo", "Payment UI loaded")
            "paymentStarted" -> Log.d("Daimo", "Payment initiated")
            "paymentCompleted" -> Log.d("Daimo", "Payment succeeded")
        }
    }
}, "ReactNativeWebView")

// Construct the URL from your session response
val url = "https://daimo.com/webview?session=$sessionId&cs=$clientSecret"
webView.loadUrl(url)
```

### React Native

```tsx theme={null}
import { WebView } from "react-native-webview";

function PaymentWebView({
  sessionId,
  clientSecret,
}: {
  sessionId: string;
  clientSecret: string;
}) {
  const uri = `https://daimo.com/webview?session=${sessionId}&cs=${clientSecret}`;

  const handleMessage = (event: { nativeEvent: { data: string } }) => {
    const msg = JSON.parse(event.nativeEvent.data);
    switch (msg.type) {
      case "ready":
        console.log("Payment UI loaded");
        break;
      case "contentHeightChanged":
        console.log("Payment UI height", msg.payload.height);
        break;
      case "paymentStarted":
        console.log("Payment initiated");
        break;
      case "paymentCompleted":
        console.log("Payment succeeded");
        break;
    }
  };

  return (
    <WebView
      source={{ uri }}
      onMessage={handleMessage}
      javaScriptEnabled
      domStorageEnabled
    />
  );
}
```

## Query parameters

| Parameter | Required | Values           | Description                              |
| --------- | -------- | ---------------- | ---------------------------------------- |
| `session` | Yes      | string           | Session ID                               |
| `cs`      | Yes      | string           | Client secret                            |
| `locale`  | No       | `es`, `fr`, etc. | UI language (default: browser locale)    |
| `theme`   | No       | `light`, `dark`  | Color theme (default: auto)              |
| `layout`  | No       | `embed`          | Renders inline; omit for modal (default) |

## Handle events

The WebView sends messages via `postMessage`. The message format:

```json theme={null}
{
  "source": "daimo-pay",
  "version": 1,
  "type": "paymentCompleted",
  "payload": {}
}
```

| Event                  | Description                            |
| ---------------------- | -------------------------------------- |
| `ready`                | Payment UI finished loading            |
| `modalOpened`          | Modal became visible                   |
| `modalClosed`          | Modal was dismissed                    |
| `contentHeightChanged` | Embedded content height changed        |
| `paymentStarted`       | User's deposit transaction is detected |
| `paymentCompleted`     | Funds delivered to destination         |

Payment events are notifications only — the `payload` object is empty. `contentHeightChanged` includes `{ "height": number }` in CSS pixels. To get full session details (tx hash, chain, amounts, etc.) after any payment event, poll [`GET /v1/sessions/{sessionId}`](/api-reference/retrieve-session).

## Fit embedded height

When you load the webview inside your own modal or bottom sheet, add `layout=embed` and size the host container to the reported content height. This avoids showing a full-screen webview with empty space below the payment UI.

```
https://daimo.com/webview?session={sessionId}&cs={clientSecret}&layout=embed
```

The page sends `contentHeightChanged` whenever its rendered height changes. In React Native, store that height and apply it to the `WebView`:

```tsx theme={null}
const [height, setHeight] = useState(480);

const handleMessage = (event: { nativeEvent: { data: string } }) => {
  const msg = JSON.parse(event.nativeEvent.data);
  if (msg.type === "contentHeightChanged") {
    setHeight(msg.payload.height);
  }
};

return (
  <WebView
    source={{ uri }}
    onMessage={handleMessage}
    style={{ height }}
    javaScriptEnabled
    domStorageEnabled
  />
);
```

## Theming

Set your org theme in the Daimo dashboard to match the hosted payment UI to your brand. The WebView loads it automatically from the session.

Use `theme=light` or `theme=dark` to force a mode. Otherwise, the UI follows the user's system preference.

See [Customization](/guides/customization) for the dashboard flow, token list, and per-session CSS override.

## sendToHost protocol

The webview page communicates with the host container via a `sendToHost` function that tries three transport layers in order:

1. **React Native / Expo** — `window.ReactNativeWebView.postMessage(JSON.stringify(msg))`
2. **iOS / macOS WKWebView** — `window.webkit.messageHandlers.daimoPay.postMessage(msg)`
3. **Browser iframe / desktop WebView** — `window.parent.postMessage(msg, "*")`

All messages share a common envelope:

```json theme={null}
{
  "source": "daimo-pay",
  "version": 1,
  "type": "<event name>",
  "payload": {}
}
```
