Guides

Send push notifications

Tell the Contact on their phone when a Member replies, with a badge, and open the chat when they tap the notification.

Updated October 28, 2026

Zean sends no push notification itself. Each Member’s reply reaches your server in the message.member event, with the notification ready in data.push: your server sends it with your push service. Your app keeps its own: expo-notifications, Firebase or another.

Receive the webhook

Set up the App’s webhook and check the signature of each call. See Receive events with a webhook and Verify a webhook signature.

Give the push token to the chat

In the app, pass the device’s push token to setPushToken() from useZean(): an Expo, FCM or APNs token. null forgets it. After a sign-out, the chat gives it again by itself. Zean puts it in push.tokens of each message.member for this Contact.

With expo-notifications. Render <ZeanPush /> once, next to ZeanChat:

File: ZeanPush.tsx

import { useEffect } from "react";import * as Notifications from "expo-notifications";import { useZean } from "@zean-chat/react-native";export function ZeanPush() {  const { setPushToken } = useZean();  useEffect(() => {    Notifications.requestPermissionsAsync().then(async ({ granted }) => {      if (granted) setPushToken((await Notifications.getExpoPushTokenAsync()).data);    });  }, [setPushToken]);  return null;}

Send the notification from your server

On message.member, send push.title and push.body to each token in push.tokens, with push.data as the notification’s data. With Expo tokens, Expo’s push service is enough:

File: zean-push.js

// Called with the event, once its signature is checked.export async function sendPush(event) {	if (event.type !== 'message.member' || event.data.push.tokens.length === 0) return;	const { title, body, tokens, data } = event.data.push;	await fetch('https://exp.host/--/api/v2/push/send', {		method: 'POST',		headers: { 'Content-Type': 'application/json' },		body: JSON.stringify(tokens.map(to => ({ to, title, body, data, sound: 'default' })))	});}
  • FCM or APNs tokens: send the same title, body and data through Firebase Cloud Messaging or Apple, as your server already does.
  • push.language gives the Contact’s language, to write your own text if you prefer.
  • Only a Member’s reply sends message.member: automatic AI replies send no event, so no notification.

Show the badge

unreadCount counts the replies the Contact has not read, and goes back to 0 when they open the chat. In ZeanPush, put it on the app’s icon:

const { setPushToken, unreadCount } = useZean();useEffect(() => {  Notifications.setBadgeCountAsync(unreadCount);}, [unreadCount]);

Open the chat on a tap

openFromNotification() opens the chat when the tapped notification comes from a Member’s reply, and returns false for any other notification. It also works when the tap starts the app, before ZeanChat renders. Put the line outside any component, in the root file:

File: App.tsx

import * as Notifications from "expo-notifications";import { openFromNotification } from "@zean-chat/react-native";Notifications.addNotificationResponseReceivedListener(r => openFromNotification(r.notification.request.content.data));

To sort your notifications yourself, isZeanNotification(data) says whether one comes from Zean, without opening the chat.

Test in development

  1. Start your server, then point the Zean CLI at its webhook route. See Receive webhooks on your computer.
  2. Open the chat in a development build of your app and write a message: the device becomes “Your test device”.
  3. In App settings > Developers > Webhook > “Test events”, pick message.member, then “Send a test event”.
  4. Zean writes a test reply in the device’s Conversation. Your server gets the event with the device’s real token and sends the notification. Tap it: the chat opens on the reply, and the badge follows.

On Android, Expo Go no longer receives push notifications since Expo SDK 53: test them with a development build.