SDKs
Four client SDKs, all MIT licensed, all talking to the same /v1 endpoints with the app's pk_live_… public key.
| SDK | Package | Platform | Transport | Notes |
|---|---|---|---|---|
| iOS (Swift) | packages/sdk-ios (Swift Package) | iOS 15+ | APNs directly | No Firebase. URLSession + UserDefaults, zero dependencies. NSE template included. |
| Android (Kotlin) | com.opennotification:sdk (AAR) | minSdk 23 | FCM | Firebase for the token; the SDK draws every notification. HttpURLConnection, no other deps. |
| React Native | @opennotification/react-native | iOS + Android | APNs (native module) / FCM (embedded Kotlin SDK) | Native modules ship in the package; no Firebase or Notifee JS packages. Expo config plugin. |
| Web | @opennotification/web | Browsers, PWAs | Web Push | Capability detection, iOS install guide, prebuilt service worker. |
What they all do
- Register the device: token + platform + device facts + the user's language/timezone/country. The API upserts by token, so calling it on every launch is correct and cheap. The returned
subscriptionIdis persisted locally. - Identity:
login(externalId)attaches the device to your user id;logout()detaches it. Rules in Identity. - Tags:
setTagsmerges,removeTagsdeletes keys. Tags belong to the user and reach every device they own. - Tracking: the SDK reports
delivered,opened,clickedand (where the OS allows)dismissedusing the signedmsgIdin the payload. No key needed — the signature is the credential. - Opens:
onOpengives you a normalised message (title,body,url,data,actionId) so you can route. Cold starts are handled — a press that launched the app is replayed once your handler is set.
The normalised message
Whatever the wire format (APNs aps+on, FCM flat data, Web Push JSON), every SDK hands your code the same shape:
| Field | Type | Notes |
|---|---|---|
msgId | string? | Signed tracking id. null if the push was not from Open Notification. |
campaignId | string? | |
title, body | string | Already in the subscriber's locale, placeholders filled. |
image | string? | |
url | string? | The deep link. If a button with its own url was pressed, that URL is here. |
actions | {id,title,url?}[] | |
actionId | string? | Set when the open came from a button. |
data | Record<string,string> | The campaign's data payload. |
Errors never throw
Every network-facing call returns a Result ({ ok: true, value } / { ok: false, error } in TypeScript; Result<…> / completion Error? in Swift and Kotlin). Codes are stable strings — permission_denied, no_token, api_error, network_error — so you can branch without parsing messages.
Which key, again
The SDKs take the public key. It can register a device and update that device's own user; it cannot send, read other users or list anything. Shipping it inside an app binary or a web page is the intended use. Never put an sk_ key in a client.