React Native

To use our SDK with React Native, you only need to check the nativeEvent field from the WebViewMessageEvent, which contains data about the specific event. See the example below:

import { useEffect, useRef, useState } from "react";
import { Linking } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { WebView, WebViewMessageEvent } from "react-native-webview";

const urlMapper = {
  cert: {
    sdkMobile: "https://sdk-mobile.cert.zerohash.com",
    zeroHashAppsURL: "https://web-sdk.cert.zerohash.com",
  },
  prod: {
    sdkMobile: "https://sdk-mobile.zerohash.com",
    zeroHashAppsURL: "https://web-sdk.zerohash.com",
  },
};

/**
 * List of permissions used to request a JWT in the `client_auth_token` request
 */
const permissions = [
  "crypto-buy",
  "crypto-sell",
  "crypto-withdrawals",
  "crypto-payouts",
  "fiat-deposits",
  "fiat-withdrawals",
  "fwc", // Fund
  "onboarding",
  "participant-profile",
  "update-participant",
  "crypto-account-link",
];

/**
 * You can copy and paste a JWT on jwtToken for testing. You will need to
 * implement a function to fetch the JWT from your backend and replace this variable.
 * We have an example below using fetchJwt and setToken functions.
 * */
const jwtToken = ``;

const App = () => {
  const webViewRef = useRef<WebView>(null);
  const [token, setToken] = useState<string>(jwtToken);
  useEffect(() => {
    fetchJwt();
  }, []);

  // Fetch JWT token from server
  const fetchJwt = async () => {
    const jwtPayload = {
      // The participant code you want to request the JWT for.
      participant_code: "<PARTICIPANT_CODE>",
      // The permission required for your use-case, refer to Permissions for
      // the allowed values
      permissions: ["fwc"],
    };
    try {
      const response = await fetch(`<API_URL_TO_FETCH_TOKEN>`, {
        method: "POST",
        body: JSON.stringify(jwtPayload),
      });
      const parsedResponse = await response.json();
      setToken(parsedResponse.message.token);
    } catch (e) {
      console.error("could not fetch jwt", e);
    }
  };

  // Open modal using injected JS script in webview
  const openModal = () => {
     // Replace "jwtToken" with "token" to use the token you got from your backend
    webViewRef.current?.injectJavaScript(
      `window.postMessage({ "type": "OPEN_MODAL", "payload": { "appIdentifier": "fund", "jwt": "${jwtToken}"}});true;`
    );
  };

  const handleMessage = (event: WebViewMessageEvent) => {
    try {
      // Parse message from webview event data checking nativeEvent field
      const parsedMessage = JSON.parse(event.nativeEvent.data);
      console.log("Received message:", parsedMessage);
      if (parsedMessage.type === "SDK_MOBILE_READY") {
        console.log("SDK is ready, opening modal...");
        openModal();
      }
      // Handle webview events
      // Your code here...
    } catch (e) {
      alert(
        `could not parse message: ${JSON.stringify(event.nativeEvent.data)}`
      );
    }
  };

  return (
    <SafeAreaProvider>
      <SafeAreaView style={{ flex: 1 }}>
        <WebView
          allowsInlineMediaPlayback={true}
          ref={webViewRef}
          onMessage={handleMessage}
          source={{
            // Double check the environment you are using and pick the correct URLs
            // accordingly
            uri: `${urlMapper.cert.sdkMobile}/v1?zeroHashAppsURL=${urlMapper.cert.zeroHashAppsURL}`,
          }}
          onNavigationStateChange={(event) => {
            if (!event.url.startsWith(urlMapper.cert.sdkMobile)) {
              webViewRef?.current.stopLoading();
              Linking.openURL(event.url);
            }
          }}
        />
      </SafeAreaView>
    </SafeAreaProvider>
  );
};

export default App;


Fiat Account link specific (Android and iOS) Updated 12/26/2025

📘

For Android the experience at this phase will not redirect the user back to the Customer application until we add a custom deeplink that shall be provided and configured internally. The work for this is ongoing and should be available soon.

Overview

This guide explains how to add special handling for Plaid authentication flows in your React Native app using an embedded browser. This provides a native iOS and Android authentication experience and automatically closes when the flow completes.

What This Does

  • Detects when users navigate to plaid.com URLs in your WebView
  • Launches native iOS ASWebAuthenticationSession or Android Embedded browser instead of loading in WebView
  • Automatically closes the authentication session when it detects completion
  • Provides better user experience with native iOS/Android authentication UI

Implementation Steps for expo

import * as WebBrowser from "expo-web-browser";
import { useRef } from "react";
import { Linking, Platform } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { WebView } from "react-native-webview";

const urlMapper = {
  cert: {
    sdkMobile: "https://sdk-mobile.cert.zerohash.com",
    zeroHashAppsURL: "https://web-sdk.cert.zerohash.com",
  },
  prod: {
    sdkMobile: "https://sdk-mobile.zerohash.com",
    zeroHashAppsURL: "https://web-sdk.zerohash.com",
  },
};

const IOS_REDIRECT_URL = "zerohashapp://done";
WebBrowser.maybeCompleteAuthSession();
const App = () => {
  const webViewRef = useRef<WebView>(null);
  const launchPlaidAuth = async (url: string) => {
    try {
      if (Platform.OS === "ios") {
        const result = await WebBrowser.openAuthSessionAsync(url, IOS_REDIRECT_URL);
      } else {
        const result = await WebBrowser.openAuthSessionAsync(url);
      }
    } catch (e) {
      console.warn("External flow error", e);
    }
  };

  return (
    <SafeAreaProvider>
      <SafeAreaView style={{ flex: 1 }}>
        <WebView
          allowsInlineMediaPlayback={true}
          ref={webViewRef}
          onMessage={handleMessage}
          source={{
            // Double check the environment you are using and pick the correct URLs
            // accordingly
            uri: `${urlMapper.cert.sdkMobile}/v1?zeroHashAppsURL=${urlMapper.cert.zeroHashAppsURL}`
          }}
          onShouldStartLoadWithRequest={(request) => {
            const url = request.url || ""
            if (url.includes("plaid.com")) { // Make sure to remove the "Platform.OS === 'ios'  "from the previous guide that was intended for iOS only
              launchPlaidAuth(url)
              return false; // prevent in-WebView navigation
            }
            return true
          }}
          onNavigationStateChange={(event) => {
            if (!event.url.startsWith(urlMapper.cert.sdkMobile)) {
              webViewRef?.current.stopLoading()
              Linking.openURL(event.url)
            }
          }}
        />
      </SafeAreaView>
    </SafeAreaProvider>
  );
};

export default App

Even if you are not using Expo you should be able to adapt opening the ASWebAuthenticationSession/Android corresponding in your own structure.