---
title: React Native - MomentPerks Prefetch Integration Guide
slug: react-native-momentperks-prefetch-integration-guide
icon: {"faIcon":"fa-brands fa-react"}
docTags: 
createdAt: 2025-08-12T14:09:26.196Z
---

# Overview&#x20;

This guide explains how to integrate the MomentScience Moments solution into your React Native application to present personalized offers during the checkout experience.

The guide covers two integration modes, both designed to preload and display offers in a WebView:

- SDK Prefetch Mode
- API Prefetch Mode

Both options deliver a high-performing native experience while maintaining a lightweight and flexible integration.

## Integration Modes

### SDK Prefetch Mode

The SDK automatically preloads and caches offers using a 0×0 WebView placed in advance of checkout. Offers are displayed instantly when the user reaches the checkout step.

- Easiest integration path
- No need to make manual API calls

### API Prefetch Mode

Your app uses a native HTTPS request to fetch offers from the Moments API and passes the response into the SDK during checkout.

- Full control over when and how offers are requested
- Ability to customize the request payload (e.g., user ID, cart value)

**In both modes,** the offers are rendered inside a WebView on the checkout screen. Offers are preloaded ahead of time, so user experience remains responsive, even when no offers are available. If no offers are returned, your app can skip rendering the offer section.

:::hint{type="info"}
To see a full implementation, [check out the MomentScience React Native Demo on GitHub](https://github.com/AdsPostX/examples/tree/main/react-native/MSSDKDemoApp-ReactNative) which includes examples for offer fetching, event handling, and WebView integration.
:::

## Requirements

To integrate the MomentScience SDK into your React Native app, ensure the following prerequisites are met:

- A valid **MomentScience SDK ID**, which you can [obtain by following these steps](https://docs.momentscience.com/getting-your-sdk-id).
- **Environment**:&#x20;
  - React: 18.0.0
  - React-Native: 0.70.0

# Integration Steps

## Step 1: Add Dependencies

### Install Dependencies

Install below packages into your react-native app:

```javascript
react-native-webview
react-native-dotenv (optional)
```

### Add Internet Permission&#x20;

To allow offer content to load, add the following permission in  To allow offer content to load, add the following permission in `android/app/src/main/AndroidManifest.xml`inside `<manifest>`but before `<application>`

```javascript
<uses-permission android:name="android.permission.INTERNET" />
```

This is required for the SDK to load offer content in both integration modes.

## &#x20;Step 2: Prefetch Offers (SDK or API)

You can prefetch offers using one of two modes:

- **SDK Prefetch:&#x20;**&#x55;ses a hidden `0×0` WebView to preload offers.
- **API Prefetch:** Uses a native API call to fetch offers, which are then injected into the SDK display template.

In both modes, you can skip showing the checkout screen if no offers are found.

### Option 1: SDK Prefetch

This method uses a hidden 0×0 WebView to preload the Moments SDK in the background. When available offers are detected, the SDK triggers an `ads_found` event in your app. This is the most lightweight integration method and is ideal when you want to minimize native code involvement.

1. **Set up the WebView:** Place a hidden 0×0 WebView on any screen before checkout. Make sure to enable the following WebView settings:&#x20;
   1. *javaScriptEnabled = true*
   2. *domStorageEnabled = true*
   3. *originWhitelist = \{\[”*”]}\*
2. **Register JavaScript Event Handler:** The Web SDK communicates with your app through JavaScript events. You must handle these events in your app to react to SDK events. Implement `onMessage` handler to listen for SDK events.

:::CodeblockTabs{indent="2"}
handleMessage

```javascript
  const handleMessage = useCallback(message => {
    try {
      const data = JSON.parse(message.nativeEvent.data);
      if (data.name === 'ads_found') {
        setOffersCount(data.total || 0);
      }
    } catch (err) {
    // handle error gracefully
    }
  }, []);
```
:::

3. Load WebView with the prefetch HTML template: Use the existing prefetch template from [`prefetch.js`](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/templates/prefetch.js). Inject your `SDKID`, `SDK CDN URL`, `AdpxUser` configuration dynamically, then load the resulting HTML into the WebView.

:::CodeblockTabs{indent="2"}
getWebSDKHtml&#x20;

```javascript
export const getWebSDKHtml = (sdkId, payload) => {
  let html = processTemplate(prefetchTemplate, {
    SDK_ID: sdkId,
    CDN_URL: SDK_CONFIG.CDN_URL,
  });

  // Create AdpxUser script with payload
  if (payload) {
    const adpxUserScript = createAdpxUserScript(payload);
    html = html.replace('window.AdpxUser = {};', adpxUserScript);
  }

  return html;
};

```

processTemplate&#x20;

```javascript
const processTemplate = (template, values) => {
  let result = template;
  Object.entries(values).forEach(([key, value]) => {
    result = result.replace(new RegExp(`%%${key}%%`, 'g'), value);
  });
  return result;
};
```

createAdpxUserScript&#x20;

```javascript
export const createAdpxUserScript = payload => {
  let adpxUserScript = 'window.AdpxUser = {';

  if (payload && Object.keys(payload).length > 0) {
    Object.entries(payload).forEach(([key, value]) => {
      // Ensure value is properly escaped for JavaScript string
      const escapedValue = String(value).replace(/"/g, '\\"');
      adpxUserScript += `\n  ${key}: "${escapedValue}",`;
    });
    // Remove trailing comma
    adpxUserScript = adpxUserScript.slice(0, -1);
  }

  adpxUserScript += '\n};';
  return adpxUserScript;
};

```

generatePayload&#x20;

```javascript
  const generatePayload = useCallback(() => {
    return {
      adpx_fp: generateUniqueValue(),
      pub_user_id: generateUniqueValue(),
      placement: 'checkout',
      themeId: 'demo',
      ua: generateUserAgent(),
    };
  }, []);

```
:::

4. **Handle&#x20;**`ads_found`**Event:&#x20;**&#x4F;nce the WebView receives the ads\_found event, extract the offer count. If offer Count == 0, you can skip rendering the offer UI entirely.

:::hint{type="success"}
To see working example of how to load prefetch html with injected configurations, refer [webSDKUtils.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/utils/webSDKUtils.js), [prefetch.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/templates/prefetch.js), [adpxUtils.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/utils/adpxUtils.js), [HomeScreen.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/screens/HomeScreen.js) in **demo app**.
:::

### Option 2: Prefetch with API&#x20;

This method allows your app to prefetch offers directly from the Moments API and pass them into the Web SDK via script injection.&#x20;

:::hint{type="success"}
For complete details on MomentPerks API, refer to the [MomentPerks API](docId\:zpOL4DX0Bohl2AOoyZrLQ)&#x20;
:::

1. **Send a&#x20;**`POST`**request to the Moments API:**

:::CodeblockTabs{indent="2"}
Moments API Endpoint

```javascript
POST https://api.adspostx.com/native/v4/offers.json
```
:::

2. **Build the Request Parameters:&#x20;**&#x49;nclude the following:
   1. `api_key` as a query parameter
   2. A user payload in the request body
   3. Custom `User-Agent` in the request headers
3. **Validate Optional Parameters (if used):&#x20;**
   1. `loyaltyboost `must be "`0`", "`1`", or "`2`"
   2. `creative `must be "`0`" or "`1`"
4. **Prepare and Send the Request:&#x20;**&#x55;se the following example code to construct and send the request:

:::CodeblockTabs{indent="2"}
fetchOffers

```javascript
// API_BASE_URL=https://api.adspostx.com/native/v4


export const fetchOffers = async ({
  sdkId,
  loyaltyBoost = '0',
  creative = '0',
  campaignId = null,
  isDevelopment = false,
  payload = {},
}) => {
  if (!sdkId) {
    throw new Error('SDK ID is required');
  }

  // Validate loyaltyBoost
  if (!['0', '1', '2'].includes(loyaltyBoost)) {
    throw new Error('Invalid loyaltyBoost value. Must be "0", "1", or "2"');
  }

  // Validate creative
  if (!['0', '1'].includes(creative)) {
    throw new Error('Invalid creative value. Must be "0" or "1"');
  }

  try {
    // Create base query parameters
    const queryParams = new URLSearchParams();
    queryParams.append('api_key', sdkId);
    queryParams.append('loyaltyboost', loyaltyBoost);
    queryParams.append('creative', creative);

    // Add campaignId if provided
    if (campaignId !== null) {
      queryParams.append('campaignId', campaignId);
    }

    // Get user agent from payload or generate it
    const userAgent = payload.ua || generateUserAgent();

    // Construct request options
    const requestOptions = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': userAgent,
      },
      body: JSON.stringify({
        ...(isDevelopment ? { dev: 1 } : {}),
        ...payload,
      }),
    };

    const response = await fetch(
      `${API_CONFIG.BASE_URL}/offers.json?${queryParams.toString()}`,
      requestOptions,
    );

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      throw new Error(
        errorData.message ||
          `API request failed with status ${response.status}`,
      );
    }

    return await response.json();
  } catch (error) {
    throw new Error(`Failed to fetch offers: ${error.message}`);
  }
};
```
:::

5. **Save the API Response Locally:&#x20;**&#x53;tore the JSON response from the API to use it when initializing the SDK.

:::hint{type="success"}
To see working implementation of API call refer[ api.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/services/api.js),[ helpers.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/utils/helpers.js) in the **demo app**.
:::

## Step 3: Show Offers Using Fullscreen WebView&#x20;

After confirming that offers are available, display them on your checkout screen using a fullscreen WebView. Pass the necessary parameters depending on the prefetch method.&#x20;

1. **Navigate to the Checkout Screen**

:::CodeblockTabs{indent="2"}
handleProceedToCheckout

```javascript
  const handleProceedToCheckout = useCallback(() => {
    const params = {
      sdkId,
      apiResponse:
        selectedPrefetchMethod === PrefetchMethod.API ? response : null, // only require for api prefetch
      prefetchMethod: selectedPrefetchMethod, // not require, as you are going to use one of the approach.
      offersCount: getOffersCount(), // optional, require only if you want to show no of offers in checkout screen
      payload: currentPayload,
      userAgent: generateUserAgent(),
    };
    navigation.navigate('Checkout', params);
  }, [
    navigation,
    sdkId,
    response,
    selectedPrefetchMethod,
    getOffersCount,
    currentPayload,
  ]);
```
:::

2. **Load the Offer experience:&#x20;**&#x49;nside the CheckoutScreen, load the offer experience into the WebView using [checkout.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/templates/checkout.js). Follow these steps:&#x20;
   1. Read html from checkout.js.
   2. Replace placeholders like `%%SDK_ID%%`, `%%AUTOLOAD_CONFIG%%`, `%%CDN_URL%%`,  `%%RESPONSE_HANDLING%%` with actual values (from the payload or API response).
   3. Ensure JavaScript, DOM storage settings are enabled in the WebView.
   4. Load the final HTML into WebView.
3. **Choose Logic Based on Prefetch Mode:** Use different logic depending on whether you're using:
   1. **Prefetch with SDK:** Cached offers are automatically shown by the SDK.
   2. **Prefetch with API:** You need to pass the saved apiResponse and inject it via JavaScript.

:::CodeblockTabs
htmlContent

```javascript
  const htmlContent = useMemo(() => {
    return getCheckoutHtml({
      sdkId,
      offersCount,
      autoLoadConfig: getAutoLoadConfiguration(),
      responseHandling: getResponseHandlingScript(),
      adpxUserScript: 
      (),
    });
  }, [
    sdkId,
    offersCount,
    getAutoLoadConfiguration,
    getResponseHandlingScript,
    getAdpxUserScript,
  ]);
```

getCheckoutHtml&#x20;

```javascript
export const getCheckoutHtml = ({
  sdkId,
  offersCount,
  autoLoadConfig,
  responseHandling,
  adpxUserScript,
}) => {
  // First replace all template variables
  let processedHtml = processTemplate(checkoutTemplate, {
    SDK_ID: sdkId,
    OFFERS_COUNT: offersCount,
    AUTOLOAD_CONFIG: autoLoadConfig,
    CDN_URL: SDK_CONFIG.CDN_URL,
    RESPONSE_HANDLING: responseHandling,
  });

  // Then replace the AdpxUser initialization
  processedHtml = processedHtml.replace(
    'window.AdpxUser = {};',
    adpxUserScript,
  );

  return processedHtml;
};
```

processTemplate&#x20;

```javascript
const processTemplate = (template, values) => {
  let result = template;
  Object.entries(values).forEach(([key, value]) => {
    result = result.replace(new RegExp(`%%${key}%%`, 'g'), value);
  });
  return result;
};
```

getAutoLoadConfiguration&#x20;

```javascript
  const getAutoLoadConfiguration = useCallback(() => {
    const isPrefetchApi = prefetchMethod === PrefetchMethod.API;
    return getAutoLoadConfig(isPrefetchApi);
  }, [prefetchMethod]);

const getAutoLoadConfig = isPrefetchApi => {
  if (isPrefetchApi) {
    return 'autoLoad: false, prefetch: false';
  } else {
    return 'autoLoad: true, prefetch: true';
  }
};

  const getResponseHandlingScript = useCallback(() => {
    const isPrefetchApi = prefetchMethod === PrefetchMethod.API;
    return createResponseHandlingScript(isPrefetchApi, apiResponse);
  }, [prefetchMethod, apiResponse]);


const createResponseHandlingScript = (isPrefetchApi, apiResponse) => {
  if (isPrefetchApi && apiResponse) {
    const apiResponseJson = JSON.stringify(apiResponse);
    return `
      if (window.Adpx && window.Adpx.setApiResponse) {
        const apiResponse = ${apiResponseJson};
        window.Adpx.setApiResponse(apiResponse).then(() => {
          console.log('API response set to Adpx, now reloading...');
          window.Adpx.reload();
        });
      }
    `;
  }
  return '';
};

  const getAdpxUserScript = useCallback(() => {
    return createAdpxUserScript(payload);
  }, [payload]);


export const createAdpxUserScript = payload => {
  let adpxUserScript = 'window.AdpxUser = {';

  if (payload && Object.keys(payload).length > 0) {
    Object.entries(payload).forEach(([key, value]) => {
      // Ensure value is properly escaped for JavaScript string
      const escapedValue = String(value).replace(/"/g, '\\"');
      adpxUserScript += `\n  ${key}: "${escapedValue}",`;
    });
    // Remove trailing comma
    adpxUserScript = adpxUserScript.slice(0, -1);
  }

  adpxUserScript += '\n};';
  return adpxUserScript;
};

```
:::

:::hint{type="success"}
Checkout [adpxUtils.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/utils/adpxUtils.js), [useCheckout.jsv](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/hooks/useCheckout.js), [CheckoutScreen.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/screens/CheckoutScreen.js) in the **demo app** for working implementation.
:::

## Step 4: Open Clicks in an External Browser

To ensure that offer clickouts open in the device’s default browser (rather than inside the WebView), listen for SDK callback events and handle the URL externally.

Handle `onNavigationStateChange` callback to open any url in external browser.

:::CodeblockTabs
handleNavigationStateChange

```javascript
  const handleNavigationStateChange = navState => {
    // If the URL is not about:blank or your domain URL, open in external browser
    if (
      navState.url &&
      navState.url !== 'about:blank' &&
      !navState.url.startsWith('https://exampledomain.com')
    ) {
      Linking.openURL(navState.url).catch(err => {
        console.error('Failed to open URL:', err);
      });
      return false; // Prevent WebView from loading the URL
    }
    return true;
  };
```
:::

:::hint{type="success"}
Checkout [CheckoutScreen.js](https://github.com/AdsPostX/examples/blob/main/react-native/MSSDKDemoApp-ReactNative/MSSDKDemoAppRN/src/screens/CheckoutScreen.js) in the **demo app** for working implementation.
:::

# Conclusion

Congratulations! You've now completed the integration of the MomentScience SDK into your React Native app.

Whether you're using SDK Prefetch Mode or API Prefetch Mode, your app is now ready to deliver personalized offers directly within the user journey, enhancing monetization while maintaining full control over offer timing and display.

***

📢 If you're running into any issues while going through the integration process, feel free to contact us at [help@momentscience.com](mailto\:help@momentscience.com)
