Logo Site EngageLab Mark Colored TransparentDocument
Search

How to Support Direct Push with FCM/APNs Tokens

Applicable Scenarios

This article applies to you if your app matches the following:

  • The app already integrates Google FCM and iOS APNs directly, holds its own FCM tokens and APNs tokens, and sends pushes using the native protocols;
  • Historical app versions did not integrate the EngageLab AppPush SDK, so a large number of existing users have no EngageLab registration_id and cannot be reached through the Create Push API;
  • You want to bring these existing users into the EngageLab push system without forcing an app upgrade, and then migrate to the SDK gradually.

Solution Overview

EngageLab provides the Device Registration API for this scenario: your server submits the FCM tokens or APNs device tokens it already holds, and EngageLab generates a uniquely associated registration_id for each of them. You can then push to these users by registration_id through the Create Push API, just like SDK-registered users, and use registration_id-based capabilities such as tags, aliases, and statistics.

flowchart LR
    token["FCM / APNs tokens<br/>you already hold"]
    register["Device Registration API<br/>/v4/devices/token/registration_id"]
    regId["registration_id"]
    push["Create Push API<br/>/v4/push"]
    device["Notification delivered<br/>to the user's device"]

    token --> register --> regId --> push --> device

Integration steps:

  1. Your server calls the Device Registration API to submit tokens in batches per platform (1–500 per request) and stores the mapping between the returned registration_id and each token;
  2. When pushing, fill the target users' registration_id into to.registration_id of the Create Push API;
  3. When a token changes (FCM onNewToken, APNs re-registration), call the Device Registration API again to obtain the new registration_id.

A common question after integration is: when a user taps the notification on their phone, does the app open the home page, or can it go directly to a target page such as an order detail page? The following sections use Android FCM as an example.

The "notification click" sections below only cover native Android FCM notifications. Click handling for iOS (APNs token registration) is out of scope for this article.

Notification Click: Conclusion

Opening a target page is possible, but it is not available automatically just because a registration_id has been registered.

  • By default, tapping the notification only opens the app;
  • Which page is shown is decided by your app's own click-handling logic;
  • To open a target page, your server must specify intent.url in the push request, and your app must provide an Activity that can respond to that action.

The Device Registration API only establishes the mapping between an FCM token and a registration_id. It does not add any page-navigation capability to your app, and these users do not go through the notification-click routing logic that depends on the EngageLab SDK. This is therefore a transitional approach; in the long run we still recommend migrating to the EngageLab AppPush SDK, as described in the "Long-Term Recommendation" section at the end.

Default Behavior: No Click Action Configured

When the push request does not configure notification.android.intent, the notification EngageLab sends to FCM carries no click_action. The tap behavior then follows FCM's default behavior entirely:

  • When the app is in the background or has been killed, FCM displays the notification automatically, and tapping it opens the app's launcher Activity (the Activity declared as MAIN/LAUNCHER in AndroidManifest.xml);
  • Whether the user ends up on the home page, the login page, or a previously opened page depends on your app's startup logic and the current task stack.

So "opens the app by default" does not mean "always returns to the home page". See the Firebase documentation: Receive messages in an Android app.

Opening a Target Page: intent.url → click_action

Mapping Chain

The EngageLab server maps the value of notification.android.intent.url in the Create Push API to the android.notification.click_action field of the FCM notification. The Android system then looks for an Activity in your app that matches that action.

flowchart LR
    pushApi["Create Push API<br/>notification.android.intent.url"]
    fcm["FCM message<br/>android.notification.click_action"]
    activity["Your app<br/>Activity matching the action"]
    target["Target business page<br/>(e.g. order detail)"]

    pushApi --> fcm --> activity --> target

For the official definition of click_action, see FCM AndroidNotification.

Step 1: Specify intent.url in the Push Request

When pushing by registration_id, add intent.url under notification.android and carry business parameters in extras:

curl -X POST https://pushapi-sgp.engagelab.com/v4/push \ -u "appKey:masterSecret" \ -H "Content-Type: application/json" \ -d '{ "from": "push", "to": { "registration_id": ["13065ffa4e1a6cc91c3"] }, "body": { "platform": "android", "notification": { "android": { "title": "Order shipped", "alert": "Your order 20260911001 has been shipped. Tap to track it.", "intent": { "url": "intent:#Intent;action=com.example.app.ORDER_DETAIL;component=com.example.app/com.example.app.OrderDetailActivity;end" }, "extras": { "order_id": "20260911001", "page": "order_detail" } } } }, "request_id": "order-shipped-20260911001" }'
              
              curl -X POST https://pushapi-sgp.engagelab.com/v4/push \
  -u "appKey:masterSecret" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "push",
    "to": {
      "registration_id": ["13065ffa4e1a6cc91c3"]
    },
    "body": {
      "platform": "android",
      "notification": {
        "android": {
          "title": "Order shipped",
          "alert": "Your order 20260911001 has been shipped. Tap to track it.",
          "intent": {
            "url": "intent:#Intent;action=com.example.app.ORDER_DETAIL;component=com.example.app/com.example.app.OrderDetailActivity;end"
          },
          "extras": {
            "order_id": "20260911001",
            "page": "order_detail"
          }
        }
      }
    },
    "request_id": "order-shipped-20260911001"
  }'

            
This code block in the floating window

Notes on the value of intent.url:

  • It uses the Android Intent URI format (intent:#Intent;...;end); it is not an arbitrary web page URL;
  • action is an action name defined by your app, and component is package name/fully qualified Activity name;
  • Within the same url, you may provide only action or only component, but providing both is recommended so the target Activity is matched precisely;
  • For more value types (open the home page, Deeplink, etc.), see the intent description in the android notification fields of the Create Push API.

Step 2: Provide a Matching Activity in Your App

In AndroidManifest.xml, declare an intent-filter on the target Activity whose action matches the one in intent.url:

<activity android:name=".OrderDetailActivity" android:exported="true"> <intent-filter> <action android:name="com.example.app.ORDER_DETAIL" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
              
              <activity
    android:name=".OrderDetailActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.app.ORDER_DETAIL" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

            
This code block in the floating window

After FCM displays the notification and the user taps it, the corresponding Activity is started with that action, and the key-value pairs in the notification's extras are placed into the Intent extras. The target Activity reads the parameters and completes the business routing:

class OrderDetailActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val orderId = intent.getStringExtra("order_id") if (orderId.isNullOrEmpty()) { // Fall back to the order list or home page when the parameter is missing startActivity(Intent(this, MainActivity::class.java)) finish() return } showOrderDetail(orderId) } }
              
              class OrderDetailActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val orderId = intent.getStringExtra("order_id")
        if (orderId.isNullOrEmpty()) {
            // Fall back to the order list or home page when the parameter is missing
            startActivity(Intent(this, MainActivity::class.java))
            finish()
            return
        }
        showOrderDetail(orderId)
    }
}

            
This code block in the floating window

If your app already has a unified routing Activity (for example, all notification taps first enter PushRouterActivity, which then dispatches to specific pages based on parameters), you can point the component in intent.url to that router Activity. It reads extras and then navigates to the business page such as order detail, so you do not need to declare a separate action for every business page.

Notes

  • Is the EngageLab SDK required? No. The chain above relies only on FCM's click_action mechanism and the Activity declarations in your app.
  • Existing click_action handling can be reused: If your app already implemented click_action handling when pushing directly through FCM, simply put the original action value into intent.url.
  • Missing click_action handling must be added: If your app currently relies only on the default open behavior, add the Activity declaration and parameter parsing described in "Step 2". Otherwise, even if the server configures intent.url, the system will not find an Activity that can respond.
  • Pass business parameters via extras: intent.url decides "which page to open"; what the page displays should be passed through extras (such as order_id) and parsed by the target Activity.
  • Behavior when the app is in the foreground: When the app is in the foreground, FCM does not display the notification automatically but calls back to your app's onMessageReceived. Whether to show a notification and how to navigate on tap are entirely up to your app.
  • How to verify: During integration testing, first use intent:#Intent;action=android.intent.action.MAIN;end to confirm the delivery chain works, then replace it with your business action to verify the target page navigation.

Long-Term Recommendation: Migrate to the EngageLab AppPush SDK as Soon as Possible

The Device Registration API is positioned as a compatibility and transition solution. It lets existing users of historical app versions be reached without integrating the SDK, and generates an associated registration_id to prepare for other EngageLab AppPush capabilities. It solves the question of "can the message be delivered", and is not a replacement for the SDK.

We recommend treating it as part of your migration strategy rather than as a long-term solution:

  • New app versions integrate the EngageLab AppPush SDK: New users obtain a registration_id through standard SDK registration and get the full notification display and click-handling capabilities directly.
  • Old app versions transition with direct tokens: Existing users continue to receive pushes via the registration_id registered through the Device Registration API. The two systems can coexist and do not need to be merged forcibly.
  • Converge gradually with version updates: As users upgrade to versions with the SDK, the number of direct-token users decreases naturally and everything eventually converges on the SDK system.

Compared with the direct approach described in this article, after integrating the SDK:

  • Notification click navigation is handled uniformly by the SDK. The three intent.url types (specific Activity, app home page, Deeplink) work without declaring actions and parsing logic one by one in your app;
  • Foreground notification display, notification styles (builder_id, style, channel_id, etc.), badges, message folding, and similar fields take full effect;
  • The SDK automatically reports device information and behaviors such as clicks, so delivery and click statistics in the console are more complete.

For integration details, see the Android SDK Integration Guide and the iOS SDK Integration Guide.

Icon Solid Transparent White Qiyu
Contact Sales