Push notifications
A push step in a journey, broadcast, or track trigger is addressed to a person. Origamy looks up every device that person has registered — their phone, their tablet, the browsers they allowed notifications in — and sends to each one through the provider for that kind of device:
| Device | Token | Provider |
|---|---|---|
| iPhone / iPad app | APNs device token | Apple (APNs) |
| Android app | FCM registration token | Firebase (FCM) |
| iOS app built on Firebase (incl. most React Native apps) | FCM registration token | Firebase (FCM) |
| Browser (Chrome, Firefox, Safari, Edge) | Web Push subscription | Web Push |
A send counts as delivered when at least one device accepts it. When a provider reports a token as dead — the app was uninstalled, the browser unsubscribed — Origamy retires that device and stops sending to it. Nothing about the person changes, and their other devices keep working.
1. Connect providers
Section titled “1. Connect providers”In Channels → Push → Connect provider, connect one provider per kind of device you have. They work side by side; see Connect messaging channels for what each needs.
Web Push needs no vendor account: enter a contact email and Origamy generates the VAPID key pair. Copy the public key from the provider’s details — your site needs it to subscribe.
2. Register devices
Section titled “2. Register devices”Apps register a device by tracking a Device Created or Updated event for the signed-in person,
and remove it with Device Deleted — the same convention Customer.io and Segment use, so it works
with every Origamy SDK and the plain HTTP API.
Register on every app start, because tokens rotate. A device registered before sign-in, with
only an anonymous ID, is attached to the person when they identify. The order of the two events
doesn’t matter: if the registration arrives after the identify, it still goes to the right person.
| Property | Required | Description |
|---|---|---|
token |
yes¹ | The FCM registration token or the hex APNs device token. context.device.token is also read. |
subscription |
web only | The browser’s PushSubscription as JSON: { endpoint, keys: { p256dh, auth } }. |
platform |
no | ios, android, or web. context.device.type is also read. |
token_type |
no | fcm, apns, or webpush. Inferred from the token when omitted — set it to fcm for an iOS app that registers FCM tokens. |
app_id |
no | The bundle ID / package name. Lets one APNs key serve several apps. context.app.namespace is also read. |
environment |
APNs | sandbox for development builds, production for App Store and TestFlight builds. If omitted, Origamy tries the APNs provider’s environment first and then the other — set it to skip the extra attempt. |
¹ Or subscription on the web.
Origamy’s SDKs cover the web and servers. Native apps send the event straight to the
HTTP API with the source’s public (wk_) write key.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let token = deviceToken.map { String(format: "%02x", $0) }.joined() #if DEBUG let environment = "sandbox" #else let environment = "production" #endif trackOrigamy("Device Created or Updated", properties: [ "token": token, "platform": "ios", "environment": environment, ])}
func trackOrigamy(_ event: String, properties: [String: Any]) { var request = URLRequest(url: URL(string: "https://events.origamy.io/v1/track")!) request.httpMethod = "POST" let auth = Data("YOUR_WRITE_KEY:".utf8).base64EncodedString() request.setValue("Basic \(auth)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try? JSONSerialization.data(withJSONObject: [ "userId": currentUserId, // the ID you identify the person with "event": event, "properties": properties, ]) URLSession.shared.dataTask(with: request).resume()}FirebaseMessaging.getInstance().token.addOnSuccessListener { token -> trackOrigamy("Device Created or Updated", JSONObject().put("token", token).put("platform", "android"))}
// In your FirebaseMessagingService — tokens rotate:override fun onNewToken(token: String) { trackOrigamy("Device Created or Updated", JSONObject().put("token", token).put("platform", "android"))}
fun trackOrigamy(event: String, properties: JSONObject) { val body = JSONObject() .put("userId", currentUserId) // the ID you identify the person with .put("event", event) .put("properties", properties) val request = Request.Builder() .url("https://events.origamy.io/v1/track") .header("Authorization", Credentials.basic("YOUR_WRITE_KEY", "")) .post(body.toString().toRequestBody("application/json".toMediaType())) .build() okHttpClient.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) {} override fun onResponse(call: Call, response: Response) { response.close() } })}With @react-native-firebase/messaging (FCM tokens on both platforms):
import messaging from '@react-native-firebase/messaging';import { Platform } from 'react-native';import { encode } from 'base-64';
const trackOrigamy = (event, properties) => fetch('https://events.origamy.io/v1/track', { method: 'POST', headers: { Authorization: `Basic ${encode('YOUR_WRITE_KEY:')}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ userId: currentUserId, event, properties }), });
async function registerDevice() { await messaging().requestPermission(); const token = await messaging().getToken(); await trackOrigamy('Device Created or Updated', { token, token_type: 'fcm', platform: Platform.OS });}
messaging().onTokenRefresh((token) => trackOrigamy('Device Created or Updated', { token, token_type: 'fcm', platform: Platform.OS }),);With Expo, Notifications.getDevicePushTokenAsync() returns the native token — APNs on iOS, FCM
on Android — so send its data as token and let Origamy infer the type. (Expo’s own
ExponentPushToken[...] tokens are for Expo’s push service and can’t be used here.)
The Web SDK does the whole flow — permission, service worker, subscription, registration — and keeps the subscription attached to whoever is signed in:
button.addEventListener('click', async () => { const subscription = await origamy.webPush.subscribe({ vapidPublicKey: 'YOUR_VAPID_PUBLIC_KEY', }); if (!subscription) { // unsupported browser, or the person declined }});Ask from a user gesture (a button), not on page load — browsers block or quiet prompts that arrive unprompted. It needs the service worker below. See the Web SDK reference for the rest of the API.
curl https://events.origamy.io/v1/track \ -u "YOUR_WRITE_KEY:" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_123", "event": "Device Created or Updated", "properties": { "token": "740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad", "platform": "ios", "environment": "production" } }'On sign-out, unregister the device — before clearing the user ID — so the next person on it
doesn’t receive the previous person’s notifications. token is enough; token_type is optional:
trackOrigamy('Device Deleted', { token });On the web, origamy.reset() does this for you.
Deleting a person’s profile also deletes their push devices. That covers devices registered under their user ID and under any anonymous ID linked to them.
The web service worker
Section titled “The web service worker”Browsers only run service workers from your own origin, so serve a one-line /origamy-sw.js
that imports Origamy’s:
// Optional: defaults for every notification.self.origamyPush = { icon: '/icon-192.png', badge: '/badge-72.png' };
importScripts('https://cdn.origamy.io/v1/origamy-sw.js');It shows the notification, opens its link (or the call to action’s) on click — focusing the
tab if it’s already open — and tells the page when the browser rotates the subscription so the
SDK re-registers it. Already have a service worker? Add the importScripts line to it and pass
its registration: origamy.webPush.subscribe({ vapidPublicKey, registration }).
Safari on iOS and iPadOS supports web push only for sites added to the Home Screen.
3. Customize the notification
Section titled “3. Customize the notification”The push template editor sets the title, the body, and:
- Image — a large image on Android and the web. iOS shows it when your app has a
Notification Service Extension
that downloads the
imageURL. - Link — opened when the notification is tapped. Native apps receive it as
urlin the notification data and route it themselves; a deep link likemyapp://orders/42works. - Call to action — a button on the web. Native apps receive it as
cta_text/cta_url.
Every field takes Liquid, so a link can be personal:
https://shop.example/orders/{{properties.order_id}}.
Only what the template sets is sent to the device — never the person’s profile.
OneSignal instead
Section titled “OneSignal instead”If your apps already use OneSignal, connect it and make it the default push provider. Origamy
then skips its own device list and addresses people by OneSignal external_id: call
OneSignal.login(userId) in your app with the same user ID you identify with in Origamy. When
OneSignal is connected but isn’t the default, it is used for people with no devices registered in
Origamy.
Self-hosting
Section titled “Self-hosting”A self-hosted data plane can also send push with platform-wide credentials, used for workspaces that haven’t connected their own:
| Variable | Description |
|---|---|
FCM_SERVICE_ACCOUNT_JSON |
Firebase service account key file contents. (FCM_SERVER_KEY is no longer read: Google shut down the legacy API.) |
FCM_PROJECT_ID |
Overrides the project in the key file. |
APNS_TEAM_ID, APNS_KEY_ID, APNS_PRIVATE_KEY, APNS_BUNDLE_ID |
APNs auth key (.p8 contents) and default bundle ID. |
APNS_ENVIRONMENT |
production (default) or sandbox. |
WEBPUSH_VAPID_PUBLIC_KEY, WEBPUSH_VAPID_PRIVATE_KEY, WEBPUSH_SUBJECT |
VAPID key pair (base64url) and contact. |
