Skip to content

Push notifications

Markdown

Every team or AI reply is pushed to each of the customer’s devices that has a push token. Concierge never asks for notification permission; you do that, when it makes sense in your app, and forward the token.

In the dashboard, open your app’s Push settings:

  • iOS: your APNs auth key (.p8), key ID and team ID. Concierge picks the sandbox or production gateway per device automatically.
  • Android: a Firebase service account JSON for FCM HTTP v1.

Without credentials, local dev logs each push instead of sending it, so you can build without them.

func application(_ app: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken token: Data) {
Concierge.registerPushToken(token) // sandbox or production is detected for you
}
// UNUserNotificationCenterDelegate on a @MainActor class. Don't mark these nonisolated.
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
let userInfo = notification.request.content.userInfo
if Concierge.handleNotification(userInfo, openChat: false) {
return Concierge.shared.isChatVisible ? [] : [.banner, .sound]
}
return [.banner, .sound]
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
Concierge.handleNotification(response.notification.request.content.userInfo) // opens the chat
}

Concierge doesn’t depend on Firebase. Forward from your own FirebaseMessagingService:

override fun onNewToken(token: String) = Concierge.registerPushToken(token)
override fun onMessageReceived(message: RemoteMessage) {
if (Concierge.handleNotification(message.data)) {
// Ours: show a notification that calls Concierge.open(context) when tapped.
return
}
// Yours.
}

handleNotification returns true for Concierge payloads and updates the unread count.

Every push carries the same data fields:

Field Value
source "concierge"
conversationId "cnv_..."
messageId "msg_..."
unread Messages this customer hasn’t read yet

APNs. The alert lives in aps: the title is the team member’s name when Show team names is on, otherwise your app’s name; the body is the message preview; badge is the unread count; thread-id is the conversation id. Data fields sit in a top-level concierge dictionary:

{
"aps": { "alert": { "title": "Kirill", "body": "Fixed in 2.4, update when you can" }, "badge": 1, "thread-id": "cnv_..." },
"concierge": { "source": "concierge", "conversationId": "cnv_...", "messageId": "msg_...", "unread": 1 }
}

FCM. A notification block (title, body) plus a data map where every value is a string:

{ "source": "concierge", "conversationId": "cnv_...", "messageId": "msg_...", "unread": "1", "title": "Kirill", "body": "Fixed in 2.4, update when you can" }

Tokens the gateway reports as invalid are cleared from the device automatically.