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

# Native WebView

> Embed the deposit UI with a raw WebView, no SDK

Load the Daimo payment UI inside any native WebView (iOS `WKWebView`, Android
`WebView`, or a plain `react-native-webview`). No SDK installation required —
create a session and construct a URL.

<Info>
  On React Native, prefer [`DaimoFrameRN`](/guides/webview) from `@daimo/sdk` —
  it handles sizing, dismissal, and deeplink hand-off for you. Use this guide
  when you can't add the SDK, or you're on native iOS / Android.
</Info>

## 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 (without the SDK)

```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`, `center` | `embed` renders inline; `center` vertically centers full-height |

## 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 in `layout=embed` |
| `paymentStarted`       | User's deposit transaction is detected            |
| `paymentCompleted`     | Funds delivered to destination                    |

Payment events are notifications only — the `payload` object is empty. In
`layout=embed`, `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
```

Only `layout=embed` sends `contentHeightChanged`. 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
  />
);
```

## Center full-height WebViews

When the WebView itself fills the screen, add `layout=center` to center the
payment UI in a full-height themed WebView shell:

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

This mode does not send `contentHeightChanged`; the host WebView should own the
full available height.

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

Choose Automatic, Light, or Dark in dashboard customization to control the
default active mode. Pass `theme=light` or `theme=dark` to override it for a
specific WebView.

See [Customization](/guides/customization) for the dashboard flow and token list.

## 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": {}
}
```
