The SDK
Android / Kotlin / Java 1.6.0 passes its tests, and com.bolrach:push goes up on Maven Central once the publishing account exists.
Until the package is out, the app makes the same calls the SDK makes, and the steps below show them. Nothing about your server changes when you switch to the SDK later.
1. Connect your Firebase project
- Most apps already have
google-services.jsonin the app module. If yours does not, register the app in the Firebase console first and drop that file in. - Then open Project settings in Firebase. Under Service accounts, the button Generate new private key gives you a JSON file, and that file is what we need.
- In the Bolrach Push console, open the app and choose that file under Android (Firebase). We store it encrypted and show only its fingerprint afterwards.
2. Get the token and register
// build.gradle.kts (app): implementation(platform("com.google.firebase:firebase-bom:<latest>"))
// implementation("com.google.firebase:firebase-messaging")
// Android 13 and newer need the POST_NOTIFICATIONS runtime permission first.
FirebaseMessaging.getInstance().token.addOnSuccessListener { token -> register(token) }
class PushService : FirebaseMessagingService() {
// Google rotates tokens. Send the new one with the same device_id and the installation keeps its id.
override fun onNewToken(token: String) = register(token)
}// No key in the app: a native registration names the app id, and Push limits how often one device can call.
val body = JSONObject()
.put("app_id", APP_ID)
.put("device_id", stableDeviceId()) // made once per install and kept
.put("anonymous_id", stableAnonymousId()) // the same, until the person signs in
.put("platform", "android")
.put("push_provider", "fcm") // "hms" on Huawei, "adm" on Amazon Fire
.put("token", token)
.put("environment", "production") // match the app's environment in the console
.put("permission_state", "granted")
val installation = postJson("https://api.bolrach.com/push/v1/installations", body)
// Keep both. The receipt token signs this device's later calls; it is not a key.
prefs.edit().putString("installation", installation.getString("id"))
.putString("receipt", installation.getString("receipt_token")).apply()A registration from the app needs no key. It names the app id, and Push limits how often one device can register, so a leaked app id cannot flood your audience. If you would rather register from your backend, make the same call there with a server key and add user_id.
With the SDK, the same steps are three lines, and its bundled BolrachMessagingService handles token rotation:
BolrachPush.initialize(applicationContext, appId = "YOUR_APP_ID")
BolrachPush.requestPermission(activity) // Android 13+ asks; older versions return true
BolrachPush.registerCurrentToken()3. Sign the person in
Link the device to your own user id after sign-in, using the receipt token from registration. Clear it on sign-out:
// PATCH https://api.bolrach.com/push/v1/installations/{installation}
// Authorization: Bearer <receipt token> X-Bolrach-App-ID: YOUR_APP_ID
{ "user_id": "user_8123" } // sign-in
{ "user_id": "", "anonymous_id": "<a new id>" } // sign-outWith the SDK this is BolrachPush.login("user_8123") and BolrachPush.logout().
4. Confirm delivery
Every push carries delivery_id and installation_id in its data. When the app posts them back, the console and your webhooks show the message as received or opened on the phone, and not only accepted by Google.
override fun onMessageReceived(message: RemoteMessage) {
val deliveryId = message.data["delivery_id"] ?: return
postJson("https://api.bolrach.com/push/v1/receipts",
headers = mapOf("Authorization" to "Bearer $receiptToken", "X-Bolrach-App-ID" to APP_ID),
body = JSONObject().put("installation_id", installationId).put("delivery_id", deliveryId).put("event", "received"))
}event is one of received, displayed, opened, clicked, actioned or converted. A repeat of the same receipt is counted once.
Huawei and Amazon Fire
Add the vendor's own push library to the app, then give us the app's credential in the console: Huawei (Push Kit) takes the AppGallery app id and client secret, and Amazon Fire (ADM) takes the client id and client secret from the Amazon developer console. We check each one with the vendor before saving it.
Register the token exactly as above, with push_provider set to hms or adm. With the SDK:
// Huawei: HmsMessageService
override fun onNewToken(token: String) { BolrachPush.registerHuaweiToken(token) }
override fun onMessageReceived(message: com.huawei.hms.push.RemoteMessage) {
BolrachPush.onDataMessage(message.dataOfMap)
BolrachNotificationRenderer.displayData(this, message.dataOfMap, R.drawable.ic_notification)
}
// Amazon: ADMMessageHandlerJobBase
override fun onRegistered(context: Context, registrationId: String) { BolrachPush.registerAmazonToken(registrationId) }
override fun onMessage(context: Context, intent: Intent) {
val data = intent.extras?.keySet()?.associateWith { intent.extras?.getString(it).orEmpty() }.orEmpty()
BolrachPush.onDataMessage(data)
BolrachNotificationRenderer.displayData(context, data, R.drawable.ic_notification)
}ADM delivers data only, so the app always draws the notification, from the bolrach_title, bolrach_body and other bolrach_ keys in the data. Push Kit shows notification messages itself. When a device later gets Google services, registering an FCM token moves the same installation over.
5. Send
POST https://api.bolrach.com/push/v1/messages
{ "app_id": "YOUR_APP_ID", "target": { "type": "user", "id": "user_8123" },
"title": "Maya replied to you", "body": "\"Same here, see you Friday\"",
"data": { "screen": "thread", "thread_id": "t_77" }, "collapse_id": "thread-t_77", "priority": 1 }Keys in data reach the app as strings. collapse_id lets a newer message on the same thread replace the older one in the notification shade.