DEV Community

Amit Kumar
Amit Kumar

Posted on

MoEngage + React Native: A Practical Guide to Events, User Tracking, and Push Notifications

---
published: false
description: "A practical guide to integrating MoEngage with React Native, including Android and iOS setup, event tracking, user identification, push notifications, and debugging events from the MoEngage dashboard."
tags: reactnative, javascript, mobile, analytics
---

# MoEngage + React Native: A Practical Guide to Events, User Tracking, and Push Notifications

When I first started integrating **MoEngage into a React Native application**, I expected the setup to be simple.

Install the SDK, initialise it, track some events, and move on.

The React Native part actually is simple.

The confusing part is everything around it.

There are several things that need to line up correctly:

- React Native SDK
- Android configuration
- iOS configuration
- Workspace ID
- Project ID
- Data centre
- Push notification configuration
- Event naming
- User identification
- Dashboard validation

Miss one of these and you can end up in a situation where the app builds successfully, the SDK appears to initialise correctly, but your events aren't where you expect them to be.

So in this post, I'll walk through the approach I used to integrate MoEngage into a React Native application and, more importantly, **how I verify that the integration is actually working**.

The goal is simple:

> Get MoEngage properly initialised, send a real event, find that event on the dashboard, and build from there.

---

## What Are We Trying to Achieve?

For my React Native application, MoEngage wasn't being used only for analytics.

The requirement included:

- Custom events
- User attributes
- User identification
- Push notifications
- In-app campaigns
- Campaign journeys
- Deep links

That's where MoEngage becomes more than an analytics SDK.

The idea is:

```text
User action
    ↓
React Native event
    ↓
MoEngage
    ↓
User profile / segmentation
    ↓
Campaign / journey
    ↓
Push / In-App / Email / SMS / WhatsApp
Enter fullscreen mode Exit fullscreen mode

Once the application sends reliable events, the marketing team can use those events to create campaigns without requiring a new mobile app release for every campaign.


1. Install the React Native SDK

The first step is straightforward:

yarn add react-native-moengage
Enter fullscreen mode Exit fullscreen mode

React Native autolinking handles the native linking, so there is no need to run:

react-native link
Enter fullscreen mode Exit fullscreen mode

After installation, there is one Android configuration that is easy to miss.

Add the moengage configuration at the root level of package.json:

{
  "name": "yourapp",
  "moengage": {
    "includeAndroidXRequiredLibraries": true,
    "richNotification": true
  }
}
Enter fullscreen mode Exit fullscreen mode

What do these options do?

includeAndroidXRequiredLibraries enables the AndroidX libraries required by the SDK.

richNotification enables richer Android push notifications, such as:

  • Images
  • Big text
  • Buttons

If you're planning to use rich push notifications, make sure this is configured.


2. My React Native Structure

I didn't want MoEngage calls scattered across different screens.

Instead, I kept the integration behind two files:

src/
└── moengage/
    ├── moengageEvents.js
    └── MoEngageManager.js
Enter fullscreen mode Exit fullscreen mode

The responsibilities are intentionally simple.

moengageEvents.js

Contains:

  • Event names
  • Event attributes
  • User attribute keys

MoEngageManager.js

Contains:

  • SDK initialisation
  • Event tracking
  • User identification
  • User attributes
  • Location
  • Logout
  • Push listeners
  • In-app listeners

This gives the application a simple interface:

Screen
   ↓
MoEngageManager
   ↓
react-native-moengage
   ↓
Native SDK
Enter fullscreen mode Exit fullscreen mode

3. Get Your MoEngage IDs

Before configuring Android or iOS, I recommend getting the identifiers from the dashboard.

There are two values you may need.

Workspace ID

MoEngage currently refers to this as the Workspace ID.

Older SDK parameters and documentation may refer to it as the App ID.

You can find it under:

Dashboard → Settings → General Settings → Workspace ID

Even though the dashboard calls it Workspace ID, the SDK still uses the parameter:

appId
Enter fullscreen mode Exit fullscreen mode

So don't let the naming difference confuse you.

Project ID

If your MoEngage workspace uses Portfolio, you also need the Project ID.

You can find it under:

Dashboard → Settings → Portfolio → Project ID

This becomes particularly important when multiple projects exist inside the same workspace.

Without the correct Project ID, your events may not be associated with the project you expect.

If you're not using Portfolio, you can skip this configuration.


4. The Data Centre Is Important

This is one of the first things I check when events aren't showing up.

Your MoEngage dashboard URL tells you which data centre your workspace uses.

For example:

Dashboard URL Android iOS
dashboard-01.moengage.com DATA_CENTER_1 .data_center_01
dashboard-02.moengage.com DATA_CENTER_2 .data_center_02
dashboard-03.moengage.com DATA_CENTER_3 .data_center_03

So if you're using:

dashboard-02.moengage.com
Enter fullscreen mode Exit fullscreen mode

your Android configuration should use:

DataCenter.DATA_CENTER_2
Enter fullscreen mode Exit fullscreen mode

and iOS should use:

.data_center_02
Enter fullscreen mode Exit fullscreen mode

My rule when debugging missing events

Before changing any JavaScript code, check:

Dashboard URL
        ↓
Data Centre
        ↓
Android / iOS SDK configuration
Enter fullscreen mode Exit fullscreen mode

They should match.


5. Keep IDs in Environment Variables

I prefer not to hardcode MoEngage credentials directly into the native code.

For example:

# .env.production

MOENGAGE_APP_ID=YOUR_WORKSPACE_ID
MOENGAGE_PROJECT_ID=YOUR_PROJECT_ID
Enter fullscreen mode Exit fullscreen mode

If you're using react-native-config, these values can then be exposed to both JavaScript and native code.

This also makes it easier to maintain separate configurations for:

  • Development
  • QA
  • Staging
  • Production

6. Android Setup

For Android, I initialise MoEngage from MainApplication.kt.

The important thing here is initialisation timing.

I initialise the SDK before React Native starts loading.

Why?

Because the application can be launched from a push notification while it is completely killed.

If the SDK isn't ready when the application launches, you can miss important notification or attribution information.

A simplified setup looks like this:

import com.moengage.core.DataCenter
import com.moengage.core.MoEngage
import com.moengage.core.config.NotificationConfig
import com.moengage.core.config.ProjectConfig
import com.moengage.react.MoEInitializer

class MainApplication : Application(), ReactApplication {

    override fun onCreate() {
        super.onCreate()

        initializeMoEngage()

        loadReactNative(this)
    }

    private fun initializeMoEngage() {

        val moEngage = MoEngage.Builder(
            this,
            BuildConfig.MOENGAGE_APP_ID,
            DataCenter.DATA_CENTER_2
        )
            .apply {

                if (BuildConfig.MOENGAGE_PROJECT_ID.isNotEmpty()) {
                    configureProject(
                        ProjectConfig(
                            BuildConfig.MOENGAGE_PROJECT_ID
                        )
                    )
                }
            }
            .configureNotificationMetaData(
                NotificationConfig(
                    smallIcon = R.drawable.ic_notification,
                    largeIcon = R.mipmap.ic_launcher,
                    notificationColor = R.color.moe_notification_color,
                    isMultipleNotificationInDrawerEnabled = true
                )
            )

        MoEInitializer.initializeDefaultInstance(
            applicationContext,
            moEngage
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

7. Android Notification Icon

One small Android detail caused me more confusion than I expected.

The notification icon is not the same as your normal application icon.

Android uses the alpha channel of the small notification icon.

So if you use a full-colour launcher icon, you may end up with something that looks like a solid white square in the status bar.

I recommend creating a dedicated:

ic_notification
Enter fullscreen mode Exit fullscreen mode

icon with:

  • White foreground
  • Transparent background
  • Proper Android density resources

For example:

mdpi
hdpi
xhdpi
xxhdpi
xxxhdpi
Enter fullscreen mode Exit fullscreen mode

Then configure it in MoEngage:

smallIcon = R.drawable.ic_notification
Enter fullscreen mode Exit fullscreen mode

8. Android Notification Colour

I also define a dedicated notification colour.

In:

android/app/src/main/res/values/colors.xml
Enter fullscreen mode Exit fullscreen mode
<resources>

    <color name="moe_notification_color">
        #FF6B00
    </color>

</resources>
Enter fullscreen mode Exit fullscreen mode

Then:

notificationColor = R.color.moe_notification_color
Enter fullscreen mode Exit fullscreen mode

9. Android Environment Configuration

If you're using react-native-config, your environment values need to reach Android's BuildConfig.

For example:

project.ext.envConfigFiles = [
    debug  : ".env",
    release: ".env.production",
]

apply from: project(':react-native-config').projectDir.getPath()
        + "/dotenv.gradle"
Enter fullscreen mode Exit fullscreen mode

Then the native code can access:

BuildConfig.MOENGAGE_APP_ID
BuildConfig.MOENGAGE_PROJECT_ID
Enter fullscreen mode Exit fullscreen mode

If you're not using react-native-config, you can also define these directly:

android {

    defaultConfig {

        buildConfigField(
            "String",
            "MOENGAGE_APP_ID",
            "\"YOUR_WORKSPACE_ID\""
        )

        buildConfigField(
            "String",
            "MOENGAGE_PROJECT_ID",
            "\"YOUR_PROJECT_ID\""
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

10. Handle Configuration Changes

For in-app campaigns, I also forward configuration changes from MainActivity.kt.

import com.moengage.react.MoEReactHelper

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    MoEReactHelper
        .getInstance()
        .onConfigurationChanged()
}
Enter fullscreen mode Exit fullscreen mode

This helps MoEngage respond correctly to configuration changes such as device rotation.


11. iOS Setup

The iOS setup follows the same basic concept but has its own native requirements.

First, add the MoEngage podspec source to your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/moengage/PodSpecs.git'

platform :ios, '15.1'
Enter fullscreen mode Exit fullscreen mode

Then:

cd ios
pod install
cd ..
Enter fullscreen mode Exit fullscreen mode

12. Configure iOS IDs

I use build settings rather than hardcoding the values.

In Info.plist:

<key>MoEngageAppId</key>
<string>$(MOENGAGE_APP_ID)</string>

<key>MoEngageProjectId</key>
<string>$(MOENGAGE_PROJECT_ID)</string>
Enter fullscreen mode Exit fullscreen mode

This allows different build configurations to use different MoEngage environments.


13. Initialise MoEngage in AppDelegate

Just like Android, I initialise the SDK early.

For example:

import MoEngageCore
import MoEngagePluginBase

private func initializeMoEngage(
    launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) {

    let appId =
        Bundle.main.object(
            forInfoDictionaryKey: "MoEngageAppId"
        ) as? String ?? ""

    let projectId =
        Bundle.main.object(
            forInfoDictionaryKey: "MoEngageProjectId"
        ) as? String ?? ""

    let sdkConfig = MoEngageSDKConfig(
        appId: appId,
        dataCenter: .data_center_02
    )

    if !projectId.isEmpty {
        sdkConfig.projectConfig =
            MoEngageProjectConfig(
                projectId: projectId
            )
    }

    MoEngagePlugin().initializeDefaultInstance(
        sdkConfig: sdkConfig,
        launchOptions: launchOptions
    )
}
Enter fullscreen mode Exit fullscreen mode

The important part is to initialise MoEngage before the normal React Native application launch flow.


14. iOS Push Configuration

For iOS push notifications, make sure the application has:

  • Push Notifications capability
  • Background Modes
  • Remote notifications

You also need to configure your APNs .p8 key in the MoEngage dashboard.

A common mistake here is assuming that receiving a device token means push is completely configured.

It doesn't.

You still need the APNs configuration on the MoEngage side for delivery.


15. Creating the MoEngage Manager

Now we get to the React Native side.

I keep all MoEngage calls inside:

src/moengage/MoEngageManager.js
Enter fullscreen mode Exit fullscreen mode

The basic responsibilities are:

init()
track()
identify()
setUserAttributes()
setLocation()
logout()
Enter fullscreen mode Exit fullscreen mode

I also keep push and in-app listeners inside this manager.


16. Initialise MoEngage

A simplified initialisation flow looks like this:

import ReactMoE, {
  MoEInitConfig,
  MoEPushConfig,
  MoEngageLogConfig,
  MoEngageLogLevel,
  MoEAppStatus,
} from "react-native-moengage";

const init = ({ isFirstInstall = false } = {}) => {

  const initConfig = new MoEInitConfig(
    MoEPushConfig.defaultConfig(),
    new MoEngageLogConfig(
      MoEngageLogLevel.DEBUG,
      true
    )
  );

  ReactMoE.initialize(
    appId,
    initConfig
  );

  ReactMoE.setAppStatus(
    isFirstInstall
      ? MoEAppStatus.Install
      : MoEAppStatus.Update
  );
};
Enter fullscreen mode Exit fullscreen mode

For development, debug logs are useful.

For production, I reduce the log level.


17. Register Listeners Before Initialisation

This is one of the implementation details I consider important.

I register listeners before calling initialize().

For example:

ReactMoE.setEventListener(
  "pushClicked",
  payload => {
    console.log("pushClicked", payload);
  }
);
Enter fullscreen mode Exit fullscreen mode

Why?

Because a push notification can launch the application from a killed state.

If the callback fires before the listener has been registered, you can lose the payload.

That can become particularly painful when the payload contains deep-link information.

My flow is therefore:

Register listeners
       ↓
Initialise MoEngage
       ↓
Start application
Enter fullscreen mode Exit fullscreen mode

18. Track Custom Events

I use a single manager method for tracking:

track(eventName, props = {}) {

  if (!isReady || !eventName) {
    return;
  }

  try {

    const properties = new MoEProperties();

    Object.entries(props).forEach(([key, value]) => {

      if (value !== null && value !== undefined) {
        properties.addAttribute(key, value);
      }

    });

    ReactMoE.trackEvent(
      eventName,
      properties
    );

  } catch (error) {

    console.log(
      "[MoEngage] trackEvent failed",
      error
    );

  }
}
Enter fullscreen mode Exit fullscreen mode

I also keep the SDK call inside try/catch.

An analytics SDK should not be able to crash a user-facing screen because a tracking call failed.

If an event fails, I would rather lose that event than break the application.


19. Identify Users

After login, I identify the user:

MoEngageManager.identify(user.id, {
  email: user.email,
  firstName: user.firstName,
  lastName: user.lastName,
  city: user.city,
});
Enter fullscreen mode Exit fullscreen mode

I also identify the user again on application launch if the user is already logged in.

This is important because it allows the anonymous profile and known user profile to be associated correctly.


20. Use Dedicated User Attributes

For known MoEngage profile fields, I prefer using the dedicated SDK methods.

For example:

ReactMoE.setUserEmailID(
  String(value)
);

ReactMoE.setUserContactNumber(
  String(value)
);

ReactMoE.setUserFirstName(
  String(value)
);

ReactMoE.setUserLastName(
  String(value)
);
Enter fullscreen mode Exit fullscreen mode

Instead of:

ReactMoE.setUserAttribute(
  "email",
  value
);
Enter fullscreen mode Exit fullscreen mode

The dedicated methods give MoEngage the semantic information it expects for the user profile and engagement channels.


21. Keep Event Names in One Place

This is where moengageEvents.js comes in.

export const EVENTS = {
  SIGNUP_COMPLETED: "signupCompleted",
  PRODUCT_VIEWED: "productViewed",
  ADD_TO_CART: "addToCart",
  CHECKOUT_STARTED: "checkoutStarted",
  PURCHASE_COMPLETED: "purchaseCompleted",
  ITEM_SHARED: "itemShared",
};

export const KEYS = {
  ITEM_ID: "itemId",
  ITEM_NAME: "itemName",
  CATEGORY: "category",
  PRICE: "price",
  CURRENCY: "currency",
  QUANTITY: "quantity",
  SHARE_CHANNEL: "shareChannel",
  LOGIN_STATE: "loginState",
  ACTION_DATE_TIME: "actionDateTime",
};
Enter fullscreen mode Exit fullscreen mode

This might look like a small thing, but it becomes extremely valuable as the number of events grows.


22. Why Event Naming Matters

Event names become part of your analytics contract.

For example:

addToCart
Enter fullscreen mode Exit fullscreen mode

and:

AddToCart
Enter fullscreen mode Exit fullscreen mode

should not be treated as the same event.

The same applies to:

itemId
Enter fullscreen mode Exit fullscreen mode

and:

item_id
Enter fullscreen mode Exit fullscreen mode

If different developers use different naming conventions, you can end up with fragmented analytics.

I prefer:

Events

addToCart
checkoutStarted
purchaseCompleted
itemShared
Enter fullscreen mode Exit fullscreen mode

Attributes

itemId
itemName
category
price
quantity
Enter fullscreen mode Exit fullscreen mode

A simple rule that works well for me:

Use camelCase and keep the naming convention consistent from day one.


23. Send the Correct Data Types

Another thing I pay attention to is data type.

If the price is numeric:

price: 2499
Enter fullscreen mode Exit fullscreen mode

don't send:

price: "2499"
Enter fullscreen mode Exit fullscreen mode

Similarly:

isLoggedIn: true
Enter fullscreen mode Exit fullscreen mode

is preferable to:

isLoggedIn: "true"
Enter fullscreen mode Exit fullscreen mode

Correct data types become especially important when building segments and conditions later.


24. Calling Events From a Screen

Once the manager and constants are in place, screen-level code becomes simple.

For example:

const onAddToCart = (item, quantity) => {

  MoEngageManager.track(
    EVENTS.ADD_TO_CART,
    {
      [KEYS.ITEM_ID]: item.id,
      [KEYS.ITEM_NAME]: item.name,
      [KEYS.CATEGORY]: item.category,
      [KEYS.PRICE]: item.price,
      [KEYS.QUANTITY]: quantity,
      [KEYS.LOGIN_STATE]: isLoggedIn
        ? "loggedIn"
        : "guest",
      [KEYS.ACTION_DATE_TIME]:
        new Date().toISOString(),
    }
  );

};
Enter fullscreen mode Exit fullscreen mode

Now the screen doesn't need to know anything about the native MoEngage SDK.

It only knows about:

MoEngageManager.track()
Enter fullscreen mode Exit fullscreen mode

25. Logout

On logout:

MoEngageManager.logout();
Enter fullscreen mode Exit fullscreen mode

This keeps the MoEngage profile lifecycle aligned with the application's authentication lifecycle.


26. The Most Important Part: How I Verify Events

Getting this far doesn't mean the integration is working.

The real test for me is:

Can I trigger an event and find it in the MoEngage dashboard?

I start with a test user.

Log in to the application using the test account and trigger something simple, such as:

addToCart
Enter fullscreen mode Exit fullscreen mode

Then open the MoEngage dashboard.

Go to:

Dashboard → Segment → Search User

Search using one of the values associated with the user:

  • User ID
  • Email
  • Mobile number
  • Name
  • MoEngage ID

27. Check User Attributes

Once I find the profile, I check the user attributes.

For example:

firstName
lastName
email
city
appVersion
Enter fullscreen mode Exit fullscreen mode

This tells me whether the user identification flow is working correctly.

It's also a good place to catch naming issues.

If I see:

firstName
firstname
Enter fullscreen mode Exit fullscreen mode

I know that two different naming conventions are being used somewhere in the application.


28. Check Events / Activity

Next, I look at the user's Events / Activity.

This is where I expect to see:

addToCart
Enter fullscreen mode Exit fullscreen mode

I then expand the event and verify the properties:

itemId
itemName
category
price
quantity
Enter fullscreen mode Exit fullscreen mode

I don't just check whether the event exists.

I also check whether the attributes and their values are correct.

An event can successfully arrive in MoEngage and still be incorrectly implemented.


29. Check Devices for Push Issues

If I'm debugging push notifications, I also check the user's device information.

I'm looking for things such as:

Platform
Device
SDK version
Push token
Enter fullscreen mode Exit fullscreen mode

If there is no usable push token, I move away from the event-tracking code and start checking the native push configuration.

This separation makes debugging much faster.


30. What If the Event Doesn't Appear?

When an event doesn't show up, this is the order I normally follow.

1. Check the data centre

For example:

dashboard-02
Enter fullscreen mode Exit fullscreen mode

should match:

DataCenter.DATA_CENTER_2
Enter fullscreen mode Exit fullscreen mode

2. Wait for the SDK to flush

SDKs can batch events.

Don't assume an event is lost immediately.

3. Check user identification

If the user is anonymous, searching by your application's user ID won't work until the identity has been associated.

4. Check event spelling

Remember:

addToCart
Enter fullscreen mode Exit fullscreen mode

and:

AddToCart
Enter fullscreen mode Exit fullscreen mode

are different.

5. Enable debug logging

During development, I use debug/verbose logging to inspect the SDK behaviour.

For example:

new MoEngageLogConfig(
  MoEngageLogLevel.DEBUG,
  true
)
Enter fullscreen mode Exit fullscreen mode

I make sure this is reduced for release builds.


My MoEngage Integration Checklist

Before I consider the integration complete, I go through this checklist:

  • [ ] Workspace ID is correct
  • [ ] Project ID is configured when Portfolio is enabled
  • [ ] Data centre matches the dashboard
  • [ ] moengage block exists in package.json
  • [ ] Rich notifications are enabled if required
  • [ ] Android SDK initialises early
  • [ ] Android notification icon is configured
  • [ ] Android notification colour is configured
  • [ ] Notification channels are configured
  • [ ] Android push permission is handled
  • [ ] Configuration changes are forwarded
  • [ ] MoEngage CocoaPods source is configured
  • [ ] iOS SDK initialises early
  • [ ] Push Notifications capability is enabled
  • [ ] Background remote notifications are enabled
  • [ ] APNs credentials are configured
  • [ ] Events are centralised
  • [ ] Attribute names follow a consistent convention
  • [ ] Correct data types are sent
  • [ ] Listeners are registered before SDK initialisation
  • [ ] Release logging is reduced
  • [ ] At least one real event is visible on the dashboard

Final Thoughts

The actual React Native integration isn't particularly large.

The difficult part is making sure all the pieces around it agree with each other.

The approach that worked best for me was keeping the JavaScript integration intentionally small:

moengageEvents.js
        ↓
Event + attribute definitions

MoEngageManager.js
        ↓
SDK interaction
Enter fullscreen mode Exit fullscreen mode

Then I treat the native Android and iOS configuration separately.

And when something doesn't work, I don't immediately start changing the event code.

I go through:

Workspace ID
      ↓
Project ID
      ↓
Data Centre
      ↓
Native SDK initialisation
      ↓
User identification
      ↓
Event tracking
      ↓
MoEngage dashboard
Enter fullscreen mode Exit fullscreen mode

Once I can trigger one real event and find it on the dashboard, I know the foundation is working.

From there, adding more events becomes much easier.

If you're integrating MoEngage into a React Native app, my biggest recommendation would be:

Don't start by adding 50 events. Start with one event, get it visible in the dashboard, verify its attributes and types, and then scale the implementation.

That one test will tell you whether your integration is actually working.

Enter fullscreen mode Exit fullscreen mode

Top comments (0)