Skip to main content
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

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)

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)

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

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 "paymentStarted":
        console.log("Payment initiated");
        break;
      case "paymentCompleted":
        console.log("Payment succeeded");
        break;
    }
  };

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

Query parameters

ParameterRequiredValuesDescription
sessionYesstringSession ID
csYesstringClient secret
localeNoes, fr, etc.UI language (default: browser locale)
themeNolight, darkColor theme (default: auto)
layoutNoembedRenders inline; omit for modal (default)

Handle events

The WebView sends messages via postMessage. The message format:
{
  "source": "daimo-pay",
  "version": 1,
  "type": "paymentCompleted",
  "payload": {}
}
EventDescription
readyPayment UI finished loading
modalOpenedModal became visible
modalClosedModal was dismissed
paymentStartedUser’s deposit transaction is detected
paymentCompletedFunds delivered to destination
Events are notifications only — the payload object is empty. To get full session details (tx hash, chain, amounts, etc.) after any event, poll GET /v1/sessions/{sessionId}.

Theming

To customize the payment UI, pass a themeCssUrl in display when creating the session. This lets you override colors, radii, and other visual tokens. See Custom theming with themeCssUrl for the full list of CSS custom properties.

sendToHost protocol

The webview page communicates with the host container via a sendToHost function that tries three transport layers in order:
  1. React Native / Expowindow.ReactNativeWebView.postMessage(JSON.stringify(msg))
  2. iOS / macOS WKWebViewwindow.webkit.messageHandlers.daimoPay.postMessage(msg)
  3. Browser iframe / desktop WebViewwindow.parent.postMessage(msg, "*")
All messages share a common envelope:
{
  "source": "daimo-pay",
  "version": 1,
  "type": "<event name>",
  "payload": {}
}