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
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.
What This Does
- Detects when users navigate to plaid.com URLs in your WebView
- Launches native iOS
ASWebAuthenticationSessionorAndroid Embedded browserinstead 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.
