App Instances (Devices)

PushExpress -- SDK and application instances API reference

Version date: 05 Feb 2024


Application flow

1. Application startup

Clients (app developers) MUST do some initialization in application code (see SDK interface).

Usually, they should provide app_id (which MUST be stored in app local storage) and SDK flow activation flag (just in-memory flag).

If no valid app_id found, or SDK flow is not activated by developer, further steps SHALL NOT be done.

1.1. Generate some local-persistent installation-token (ic_token)

Clients need to have some independent globally-unique installation id, even if no internet access available.

This id often used in multiple ad-platforms, and very useful for clients who do not have their own user base with their own ids.

We suggest to use UUIDv4 or UUIDv7 as easily accessible on any modern programming languages.

Of course, clients can generate and store this ID on their own, but it is more convenient to do this at the SDK level.

So, you need:

  1. Generate UUIDv4 (or UUIDv7) to use as ic_token

  2. Store ic_token in local storage.

import java.util.UUID

const val icToken = UUID.randomUUID().toString()

// store icToken in app restart-persistent local storage!!!

1.2. Check if it is first start after installation

Access some variable from app persistent local storage, for example ic_id (PushExpress App Instance Id).

If non-nil and non-empty variable found, assume it is existing install and go to section 1.3.

Else, do the following steps:

  1. Get ic_id as specified here: get id for new app instance

  2. In case of any error (non-200 response or invalid response object), retry infinitely with exponential backoff algorithm, better with random initial value between 1-5s, capped at maximum delay of about 120s

  3. Store aquired ic_id in local storage. Storage MUST be resistant to app updates and MUST be cleared on app removal. Storage SHOULD not overlap with existing app storages (for use as library). If it is not possible, use a more distinguishable field name (px_ic_id)

  4. Go to section 2.

1.3. Startup on existing installation

As soon as possible after app startup issue first update app info request as described in section 2.

Periodically update app info with received update_interval_sec interval.

2. Update app info

  1. Gather some device and app info params like FCM token, country, language, timezone, app onscreen count and time, etc

  2. Issue update app info request (only some fields are required, other can be updated later)

  3. In case of any error (non-200 response or invalid response object), retry infinitely with exponential backoff algorithm, better with random initial value between 1-5s, capped at maximum delay of about 120s

  4. Repeat update app info request with received update_interval_sec interval

Note: it's better to update current app state (on screen/background) and send actual onscreen_count and onscreen_sec.

3. Subscribe and listen transport messages

Subscribe for transport channel (for example, Firebase Cloud Messaging).

3.1. Firebase Cloud Messaging transport

Incoming message id MUST be registered by extracting data field px.msg_id and send it in clicked event.

Notification MUST be displayed with title, body, image and icon, based on standard Firebase notification fields. Or manually by corresponding px.* fields in data.

After notification has been displayed, delivered event SHOULD be send. If not possible to detect displaying, send it after FCM message has been received.

FCM transport message format:

{
  "notification": {
    "title": "hello there",
    "body": "hello",
    "image": "https://avatars.githubusercontent.com/u/108216418",
    "icon": "https://avatars.githubusercontent.com/u/108216418"
  },
  "data": {
    "px.msg_id": "1828471_1721.7f288d0f-a026-4960-aa04-0c834f07a3ea.5",
    "px.title": "hello there",
    "px.body": "hello",
    "px.image": "https://avatars.githubusercontent.com/u/108216418",
    "px.icon": "https://avatars.githubusercontent.com/u/108216418"
  }
}

4. Gather app lifecycle (on screen, background) events

Application startups and screen time SHOULD be tracked and saved in local storage, for sending correct onscreen_count and onscreen_sec fields in update app info request.

App lifecycle events MAY be sent when app is shown on screen or goes to background.

SDK interface

Below is an example of Kotlin SDK interface:

import com.pushexpress.sdk.main.SdkPushExpress

const val PUSHEXPRESS_APP_ID = "####-######"

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        SdkPushExpress.initialize(PUSHEXPRESS_APP_ID)
        SdkPushExpress.setExternalId("user_registration_id_1234") // optional
        SdkPushExpress.setTag("ad_id", getAdvertisingIdOrEmpty()) // optional
        SdkPushExpress.setTag("offer", "google_ads_12321") // optional
        SdkPushExpress.activate() // Don't forget to activate SDK workflow!
}

API v2 reference

Get id for new app instance

Request:

curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances" \
     --data '
{
    "ic_token": "212604ab-4b19-4d24-b8ec-3b8bac401a6a",
    "ext_id": ""
}
'

Body params:

  • ic_token, required. Globally unique, local-generated app instance installation token, as described in section 1.1.

  • ext_id, optional. External id (for login/logout functionality).

Response:

  • 200: New app instance created

    {
      "id": "737112",
      "just_created": true
    }
    • id, string. Created app instance id (use as :ic_id in update info requests)

    • just_created, bool. Logged in to existing instance (via ext_id) or created new device.

Update app instance info

Request:

curl --header "content-type: application/json" \
     --request PUT \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/info" \
     --data '
{
    "transport_type": "fcm",
    "transport_token": "dmC6c1abSiaSszJTAGhDpr:APA91bG..._TelL7kq8H0FN-NfKGSd",
    "platform_type": "android",
    "platform_name": "android_api_33",
    "ext_id": "<external_user_id|ic_token>",
    "lang": "en",
    "country": "BR",
    "tz_sec": -3600,
    "tags": { "offer": "google_ads_12321" }
}
'

Body params:

  • transport_type, required. Push transport type. One of fcm, onesignal, huawei, apns

  • transport_token, optional. transport_type specific token for receiving push

  • platform_type, required. Notification rendering platform. One of android, ios, browser

  • platform_name, optional. Readable platform name and version, like, android_api_33 or chrome_118

  • country, recommended. Sim card, locale or some other platform-based country code.

    Accepts ISO3166-1, alpha-2 country code.

    MUST NOT be determined by current network IP, better send nothing!

    Example (don't forget to check exceptions and return empty string on any):

    val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
    return tm.simCountryIso.takeIf { it.isNotEmpty() } ?: tm.networkCountryIso.takeIf { it.isNotEmpty() }
  • lang, highly recommended. Platform-based language. Accepts BCP 47 language tag

    Example: Locale.getDefault().language

  • tz_sec, recommended. Platform timezone east-shift from UTC-0, in seconds, like 3600, or -7200. Example: TimeZone.getDefault().rawOffset / 1000

  • tags, optional. User-specified string tags, map of strings with string keys. Predefined tag names: ad_id, offer, webmaster

  • onscreen_count, optional. How many times app was opened and showed on screen (how many times the app was used)

  • onscreen_sec, optional. How many seconds app was on screen (how much time the app was used)

  • agent_name, recommended. Client agent (or SDK) name and version, like my_sdk_0.0.0

Response:

  • 200: App instance info updated

    {
      "id": "737112",
      "update_interval_sec": 120
    }
    • id, string. App instance id

    • update_interval_sec, int. Interval for sending update info requests in case of success

Notification events

Request:

curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/events/notification" \
     --data '
{
    "msg_id": "1828471_1721.7f288d0f-a026-4960-aa04-0c834f07a3ea.5",
    "event": "clicked"
}
'

Body params:

  • msg_id, required. Message id (for FCM transport, get from px.msg_id data filed)

  • event, required. Event name, one of:

    • delivered -- if possible, send after the notification has been successfully displayed to the user. Else, after the push message has been received from transport. Or do not send at all on any difficulties

    • clicked -- MUST be sent after the user clicks on the notification

Response:

  • 202 Accepted, with no content type and empty body

Lifecycle events

Request:

curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/events/lifecycle" \
     --data '
{
    "event": "onscreen"
}
'

Body params:

  • event, required. Event name, one of:

    • onscreen -- app opened (appeared on the screen)

    • background -- app gone to background (disappeared from screen)

    • closed -- app closed (if can be detected)

Response:

  • 202 Accepted, with no content type and empty body

Error handling

All HTTP response codes above 400 MUST be considered as error.

In general, errors do not require attention and do not need to be handled in any way.

There are two types of errors.

  • Backend errors, will always have content-type: application/json header and json response body

    Example: {"message": "validation error: ..."}

  • Other errors from proxy servers, load balancers, etc. There may or may not be some explanation in response body.

    Example: HTTP 504 Gateway Timeout, with empty response body

Last updated