WebSDK React-Native Implementation

A guide to load the Cardlytics Embedded SDK inside a React Native WebView.

Prerequisites

  • React Native 0.71 or newer
    • react-native-webview installed

Step 1: Minimal HTML host for the SDK

WebView will load a tiny HTML string that includes the SDK and bridges to React Native.

What this does:

  • Loads the Cardlytics SDK script
  • Defines getSession to request a token from React Native
  • Exposes open and close methods and forwards events to the app
<!doctype html>
  <html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://publisher-cdn-us.cardlytics.com/embedding-sdk/v1/cardlytics-embedding-sdk.js"></script>
    <style>
      html, body, #cdx-root { height: 100%; width: 100%; margin: 0; }
      #cdx-root { position: fixed; inset: 0; background: #fff; }
    </style>
  </head>
  <body>
    <div id="cdx-root"></div>
    <script>
      // Promise bridge for RN <-> WebView
      const pending = {};
      let seq = 0;
      function askNative(type, payload) {
        return new Promise((resolve, reject) => {
          const id = ++seq;
          pending[id] = { resolve, reject };
          window.ReactNativeWebView.postMessage(JSON.stringify({ id, type, payload }));
          setTimeout(() => {
            if (pending[id]) {
              delete pending[id];
              reject(new Error('Timeout waiting for native response'));
            }
          }, 5000);
        });
      }

      window.addEventListener('message', (evt) => {
        try {
          const msg = JSON.parse(evt.data || '{}');
          if (msg.replyTo && pending[msg.replyTo]) {
            pending[msg.replyTo].resolve(msg.payload);
            delete pending[msg.replyTo];
          }
        } catch {}
      });

      async function getSessionImplementation() {
        // Ask native side for a fresh session token
        const data = await askNative('getSession');
        return data; // { sessionToken, isLoggedIn }
      }

      const { open, close, show, hide } = CardlyticsEmbeddedSDK.create({
        applicationId: 'YOUR_APPLICATION_ID',
        getSession: getSessionImplementation,
        autoOpen: true,
        onShow: () => window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'onShow' })),
        onHide: () => window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'onHide' }))
      });

      // Allow native to call open or close if needed
      window.__CardlyticsControl__ = { open, close, show, hide };
    </script>
  </body>
</html>

Replace YOUR_APPLICATION_ID with the value from Cardlytics.

Step 2: React Native WebView component

// CardlyticsScreen.tsx
import React, { useCallback, useRef, useState } from 'react';
import { ActivityIndicator, SafeAreaView, View } from 'react-native';
import WebView, { WebViewMessageEvent } from 'react-native-webview';
import axios from 'axios';

const html = `<!doctype html><html>... THE HTML FROM STEP 1 ...</html>`;

export default function CardlyticsScreen() {
  const webRef = useRef<WebView>(null);
  const [loading, setLoading] = useState(true);

  const onMessage = useCallback(async (e: WebViewMessageEvent) => {
    try {
      const msg = JSON.parse(e.nativeEvent.data || '{}');
      if (msg.event === 'onShow') return;
      if (msg.event === 'onHide') return;

      if (msg.id && msg.type === 'getSession') {
        const resp = await axios.post('/api/get_cardlytics_session');
        const payload = resp.data;
        const reply = { replyTo: msg.id, payload };
        webRef.current?.postMessage(JSON.stringify(reply));
      }
    } catch (err) {
      console.warn('Bridge message error', err);
    }
  }, []);

  const open = useCallback(() => {
    const js = `window.__CardlyticsControl__ && window.__CardlyticsControl__.open();true;`;
    webRef.current?.injectJavaScript(js);
  }, []);

  const close = useCallback(() => {
    const js = `window.__CardlyticsControl__ && window.__CardlyticsControl__.close();true;`;
    webRef.current?.injectJavaScript(js);
  }, []);

  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
      <View style={{ flex: 1 }}>
        {loading && (
          <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, alignItems: 'center', justifyContent: 'center' }}>
            <ActivityIndicator />
          </View>
        )}
        <WebView
          ref={webRef}
          originWhitelist={["*"]}
          source={{ html }}
          onLoadEnd={() => setLoading(false)}
          onMessage={onMessage}
          javaScriptEnabled
          domStorageEnabled
          setSupportMultipleWindows={false}
          onShouldStartLoadWithRequest={(req) => {
            if (!req.mainDocumentURL?.startsWith('about:blank')) return false;
            return true;
          }}
        />
      </View>
    </SafeAreaView>
  );
}

Step 3: Wire it into your app

// Example with React Navigation
import { Button } from 'react-native';

function Home({ navigation }) {
  return <Button title="Open Rewards" onPress={() => navigation.navigate('Cardlytics')} />;
}