Push notifications

Wake your customers' apps with native push when a message or call arrives and the recipient device is offline. The platform fans notifications out through APNs (iOS), FCM v1 (Android), and Web Push / VAPID (browser), using credentials you upload to the dashboard. The notification body is sender-supplied cleartext metadata only, never the encrypted message content.

How it works

  1. You configure per-platform credentials once per app: an APNs .p8 key + Team / Key / Bundle IDs, an FCM service-account JSON, and/or a VAPID keypair + subject. All key material is AES-256-GCM encrypted at rest with a master key separate from your data.
  2. Your client SDK calls registerPushToken({ platform, token, voipToken? }) after the OS hands it a device token. The token is opaque to the platform and stored per (app, user, device, platform).
  3. When a sender attaches an optional pushPayload (title, body, badge, sound, threadId, category, voip) to a message, the platform fans the push out via the relevant provider only if the recipient device has no live WebSocket connection. Live deliveries don't double-notify.
  4. Token-invalid responses from providers (APNs Unregistered / BadDeviceToken, FCM UNREGISTERED, Web Push 404/410) automatically purge the dead token so subsequent sends skip it.

E2EE invariant

For end-to-end encrypted messages the body never leaves the device in plaintext. pushPayload carries only the sender's cleartext heads-up ("1 new message from Alice"), formatted by the platform into the APNs / FCM / Web Push payload as-is. When the user taps the notification, the SDK reconnects its WebSocket and decrypts the real message locally. This matches the Signal / Wire pattern and never exposes E2EE plaintext to a third-party provider.

Quick start

JavaScript

await client.registerPushToken({ platform: 'WEB_PUSH', token: JSON.stringify(subscription) });

// On the sender side: attach pushPayload to wake the recipient
await client.sendMessage('bob', plaintext, {
  pushPayload: {
    title: 'Alice',
    body: 'sent you a message',
    threadId: 'alice:bob',
  },
});

Android (Kotlin)

FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
  if (task.isSuccessful) {
    DropOnAir.getInstance().registerPushToken("FCM", task.result)
  }
}

iOS (Swift)

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let hex = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    Task {
        try await DropOnAir.shared.registerPushToken(platform: "APNS", token: hex)
    }
}

VoIP / CallKit (iOS)

For incoming call invites you can pair the regular APNs token with a PushKit VoIP token in one registerPushToken call. When a caller sets pushPayload.voip = true on their CALL_INVITE frame, the platform routes the push over the VoIP channel so the callee's device wakes immediately and CallKit shows the system ringer screen, even when the app is suspended. Apple requires VoIP pushes to be reported to CallKit within 30 seconds.

Configuring credentials

Open your app in the dashboard and pick Push notifications from the left sidebar. Each platform is configured independently and stored encrypted at rest; the dashboard never shows raw key material once saved. Credentials are per-app, not per-(app, env), if you need a dev/prod split create two apps.

Compatibility

Requires SDK 0.9.0+ on JavaScript, Android, and iOS (PROTOCOL_VERSION 5). Verify the platform supports it by calling GET /api/info and checking that features includes "push_notifications".