App integration
React Native
Add Reception’s native iOS support chat to a React Native or Expo app. The official Swift SDK handles the chat, photos, replies, and storage; your JavaScript connects it to your app.
Requires Expo SDK 54+ (verified 54–57), with its default React Native and React versions, iOS 17+, Xcode 26, and Expo Modules. Expo Go cannot load Reception; use a development build.
1. Install
npx expo install react-native-receptionAdd the plugin to your existing app config, preserving other plugins:
{
"expo": {
"plugins": ["react-native-reception"]
}
}For generated native projects, apply plugins even when ios/ already exists, then build:
npx expo prebuild --platform ios
npx expo run:ios2. Configure
Configure once in your existing async startup function, before identity or chat. Keep Support disabled until it completes and handle failures with your app’s error UI. Copy your public App ID from Settings → your app → General:
import { Reception } from 'react-native-reception';
// Reception support chat. Docs for changes: https://reception.sh/llms.txt
await Reception.configure({ appId: 'app_YOUR_APP_ID' });Methods return promises to run native work on the main queue. Await configuration before reading Reception.isConfigured; this checks accepted configuration, not connectivity. Registration begins with the first valid message send.
3. Open chat
Copy your app’s existing button style and call Reception.openChat() on press:
import { Button } from 'react-native';
import { Reception } from 'react-native-reception';
// Inside your screen; use your own button component and styling.
<Button title="Support" onPress={() => void Reception.openChat()} />;await Reception.closeChat() dismisses the sheet. Your app owns the Support entry and its unread badge.
Appearance and language
Read accentColor, onAccentColor, chatBackground, incomingBackground, incomingForeground and fontFamily from matching values in the app’s existing theme. Leave unmatched options unset; never invent colors. Pass both light and dark theme colors so the native chat follows the app’s color scheme. Assign a complete local appearance object. Omitted fields restore Swift defaults; invalid fields log and retain their previous value:
// theme is your app’s existing theme; use only fields it actually provides.
Reception.appearance = {
accentColor: { light: theme.light.accent, dark: theme.dark.accent },
onAccentColor: { light: theme.light.onAccent, dark: theme.dark.onAccent },
};
// Only if your app has its own language picker:
Reception.languageOverride = selectedLanguage;Each color accepts #RRGGBB, #RRGGBBAA, or { light, dark } with both values in either hex format. This applies to all six colors, including welcomeIconColor. Pairs follow the native color scheme without JavaScript updates. Enable automatic appearance in your app config if your app follows the system. The 23 options correspond to the Swift appearance options: colors, title and welcome copy, close button, light/dark mode, font design/family, older-message fading, welcome icon size/position/rounding, and text visibility. Reassign when the app’s own theme selection changes; mutating a nested property does not update native styling.
Use closeIcon: 'xmark', 'chevron.down', or 'arrow.down'; fontDesign accepts default, rounded, serif, or monospaced. preferredColorScheme accepts light, dark, or null. welcomeIcon accepts { symbol: 'star' }, { image: 'Welcome' }, { hidden: true }, or null. closeImage and welcome images use names from the host iOS asset catalog. fontFamily is a registered iOS font name.
Published dashboard appearance overrides local options by default. Set Reception.usesRemoteAppearance = false for local-only styling. Set languageOverride = null to follow the app’s selected iOS localization. Existing messages and your supplied text are not translated.
Setting getters reflect valid assignments immediately, then reconcile with native validation. Live state updates asynchronously from native snapshots, including after a JavaScript reload. Assign settings during startup; await methods when subsequent work depends on completion.
Entry and unread badges
Copy the app’s existing button or settings-row component and styling; localize the label through its existing translation system. In that component, call Reception.openChat() on press and use the unread hook for a live count. For example, if the app already has a SettingsRow with title, onPress and badge props:
import { Reception, useReceptionUnreadCount } from 'react-native-reception';
import { SettingsRow } from './SettingsRow'; // Your existing component.
export function SupportRow() {
const unread = useReceptionUnreadCount();
return (
<SettingsRow
title="Support"
onPress={() => void Reception.openChat()}
badge={unread > 0 ? unread : undefined}
/>
);
}Adapt props and the translated title to your component. Put the same live badge on every entry and intermediate menu row; badge only the tab with the main entry using your navigator’s badge option. Follow the setup prompt’s badge placement and style rules.
Place hooks inside your own React component. Hide badges at zero:
import {
useReceptionUnreadCount,
useReceptionChatOpen,
} from 'react-native-reception';
const unread = useReceptionUnreadCount();
const chatOpen = useReceptionChatOpen();Outside React, read Reception.unreadCount and Reception.isChatOpen, or subscribe with addUnreadCountListener and addChatOpenListener. Each returns a subscription with remove(); remove it when its owner unmounts. Visibility reports actual chat appearance, not just an open request.
Reception.updatesAppBadge defaults to true. Assign false if your app owns the icon badge and notification cleanup. In-app unread counts still work.
Accounts and user details
After configuration, identify the current account and supply the complete metadata dictionary:
await Reception.identify({
userId: 'user_123',
name: 'Alex',
email: '[email protected]',
});
await Reception.setMetadata({ plan: 'Pro', provider: 'RevenueCat' });These values are staged for the next device update. Metadata values must be strings, at most 20 fields. Each call replaces the whole dictionary; keep one owner and update it whenever chosen fields change. Identification labels this device; it does not authenticate your app’s account or restore another device’s history. Pass null for an unavailable user ID. See Users & accounts for field limits and behavior.
For verified identity, fetch a signed token from your own server, then call await Reception.identify({ token }). Never include your identity secret in JavaScript or the app bundle. Fetch a fresh token at signed-in startup. Before supplying an asynchronously fetched token, check that the same account is still signed in; a sign-out or account switch during the request must not attach the old identity. In your existing Reception.onEvent handler, refresh only when event.type === 'identityRejected' and event.error.code === 'identity_token_expired'. Other rejection codes require fixing your server configuration; do not keep retrying them. Preserve other event handling and the logout/deletion hooks. Follow verified users for the server contract; use these JavaScript calls instead of the Swift examples.
Supply a user ID only for a real signed-in account; skip generated guest or anonymous IDs. With an entered name but no sign-in, call identify({ userId: null, name }) at launch and whenever it changes. Names and emails belong in identify, never metadata.
Call await Reception.logout() when a real signed-in session ends (sign-out, account switch or an expired restored session), before identifying another user. Never log out a guest at startup or before their first sign-in. Reapply identity, metadata, and the APNs token for the next session. Logout retains server history.
For account deletion, call deleteData() before logout and keep the session available on failure:
import { Reception, ReceptionError } from 'react-native-reception';
try {
await Reception.deleteData();
// Continue your account-deletion flow.
} catch (error) {
if (error instanceof ReceptionError) {
// Use error.code and error.status for diagnostics; offer retry.
}
}Deletion concerns this device’s support data, not your app’s account or other devices. Completion can be local when Reception no longer recognizes the session; see deletion limits.
Push
Install expo-notifications with npx expo install expo-notifications, add its config plugin, then run npx expo prebuild --platform ios and rebuild generated native projects. For maintained native projects, apply the capability and entitlement steps in Build and simulator checks. Configure Apple push credentials and the matching bundle identifier in Push notifications. Keep your existing permission flow; this helper never requests permission.
After Reception is configured, start the optional helper once in your root component and return its cleanup:
import { startReceptionPush } from 'react-native-reception/expo-notifications';
useEffect(() => startReceptionPush(), []); // Import useEffect from React.It registers the raw APNs token when already authorized, checks again on foreground and after logout() or successful deleteData(), forwards token changes, handles foreground receipt and opens chat on taps, including cold starts. It does nothing on Android and web. Remove any older Reception notification listeners to avoid routing twice. Other notification services and their listeners stay in place. The main package import does not load expo-notifications.
After your existing permission request completes, call the nonprompting refresh, even if the app stayed in the foreground (for example, after granting provisional permission):
import { refreshReceptionPush } from 'react-native-reception/expo-notifications';
await refreshReceptionPush(); // After your app’s permission request; the helper must be started.In your existing notification handler, compose the presentation policy with receptionNotificationBehavior. It returns null for other notifications, shows Reception banners/sound while chat is closed, and hides them while chat is open. It only selects presentation; the started helper owns routing. For an app with no existing handler:
import * as Notifications from 'expo-notifications';
import { receptionNotificationBehavior } from 'react-native-reception/expo-notifications';
Notifications.setNotificationHandler({
handleNotification: async (notification) =>
receptionNotificationBehavior(notification) ?? {
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: true, // Replace this fallback with your app's existing policy.
},
});Reception sends APNs directly. The helper reads the original payload in request.trigger.payload, never an Expo token or FCM registration token. It deduplicates receipt and tap callbacks, including retained responses after a JavaScript reload, and never clears Expo’s shared last-response storage. Your app’s notification router should leave Reception routing to this helper.
React Native Firebase
Keep Firebase for your app’s FCM notifications. Reception sends raw APNs without gcm.message_id, so Firebase’s onMessage, onNotificationOpenedApp and getInitialNotification do not route Reception notifications. Use the expo-notifications helper above for Reception (including its APNs registration), or forward raw notifications through your existing native notification-center delegate using the native push helpers.
If you keep APNs-token registration in Firebase with that native delegate, use this after configuration at startup, foreground, after logout/deletion and after the app’s existing permission flow. It never prompts:
import { Platform } from 'react-native';
import { Reception } from 'react-native-reception';
import {
getMessaging,
getAPNSToken,
hasPermission,
AuthorizationStatus,
registerDeviceForRemoteMessages,
} from '@react-native-firebase/messaging';
async function registerReceptionFirebaseToken() {
if (Platform.OS !== 'ios') return;
const messaging = getMessaging();
const permission = await hasPermission(messaging);
if (
permission !== AuthorizationStatus.AUTHORIZED &&
permission !== AuthorizationStatus.PROVISIONAL &&
permission !== AuthorizationStatus.EPHEMERAL
)
return;
await registerDeviceForRemoteMessages(messaging);
const token = await getAPNSToken(messaging);
if (token) await Reception.setPushToken(token);
}Firebase’s onTokenRefresh supplies an FCM token: call this function again instead of forwarding that value. Native APNs callbacks can forward Data with Reception.shared.setPushToken. Preserve unrelated routing; the native foreground helper hides only Reception banners while chat is open. Do not install both Reception routing paths.
setPushToken(token, environment) accepts automatic (default), sandbox, or production; leave automatic unless you know the signing environment. Verify actual APNs delivery on a physical iPhone.
Only if your own JavaScript receives the raw APNs payload and you use neither routing path above, forward it yourself:
const handled = await Reception.handlePushNotification(payload, { openChat: true });Pass openChat: false for receipt and true (the default) for taps, including cold starts. It returns true for a Reception payload and false for other notifications, which stay with your router. It only routes; suppress Reception banners while Reception.isChatOpen in your presentation policy.
Paywalls
Use the matching React Native paywall helper for RevenueCat, Superwall, or your own screen. Start it after Reception and your vendor are configured. Reception opens purchase screens your app already owns. Register their stable IDs, then supply your existing presentation and eligibility functions:
Reception.onPaywall = (id) => showYourPaywall(id);
Reception.paywalls = [{ id: 'plans', title: 'Plans' }];
Reception.paywallAvailability = async (id) => canShowYourPaywall(id);showYourPaywall and canShowYourPaywall above are functions from your app. Present the purchase screen above chat; Reception keeps chat open. Titles are for your team in the dashboard, not customers.
Without an availability callback, all registered paywalls are available. A callback filters Reception.availablePaywalls; observe changes with addAvailablePaywallsListener. Callback failures, timeouts, and a JavaScript reload make pending offers unavailable. Register callbacks again at startup. After subscription changes, call await Reception.refreshPaywalls({ invalidate: true }); the new result arrives asynchronously.
Assign null to remove a callback. A card needs both an available ID and onPaywall. See Paywall cards for dashboard use. Card taps report opens, not purchases.
Embedded chat
After configuration, render the native view in a container with a defined height:
import { ReceptionChatView } from 'react-native-reception';
// Inside your screen's render:
<ReceptionChatView style={{ flex: 1 }} />;Your screen owns dismissal. Do not also call openChat() for this presentation; closeChat() only dismisses the SDK sheet. Unmount the embedded view when leaving that screen.
Events and diagnostics
Assign Reception.onEvent to receive { type } events: chatOpened, messageSent, imageSent, reviewOpened, paywallOpened, and dataDeleted. identityRejected also carries a typed error with code and status. Assignment replaces the previous callback; null removes it. The event meanings match Swift.
Reception.logLevel accepts off, error, info (default), or debug. Reception.version reports the bundled Swift SDK version. No custom log handler is exposed. Calls before configuration follow the Swift SDK’s behavior: they log and do not crash.
Build and simulator checks
The plugin sets the minimum iOS deployment target to 17; report an older app target before changing it. If you already use expo-build-properties, set its ios.deploymentTarget to 17.0 or higher; Reception reports conflicting values regardless of plugin order. No Podfile edits or use_frameworks! are needed. The npm package includes its pinned Swift sources and resources.
For native projects you maintain by hand, do not regenerate them with prebuild. Set iOS 17+ in the app’s Xcode build settings and existing CocoaPods deployment-target configuration, run pod install from ios/, and rebuild. For push, also add Push Notifications in Signing & Capabilities, keeping the signed aps-environment entitlement and your notification delegate integration.
An existing bare React Native app needs Expo Modules installed and an iOS 17 deployment target. Rebuild after adding Reception.
Android and web have no Reception native code. Calls do nothing and warn once; unread count is zero, chat visibility is false, and the embedded view renders nothing.
Build with npx expo run:ios --device "<simulator name>", open the development server in the app, and keep Metro running. The setup prompt’s -ReceptionSetupCheck and -ReceptionPreview launch arguments work after JavaScript configures Reception, just as in native iOS. Use a normally signed Debug simulator build.
If macOS automation prevents Expo CLI from opening Simulator, build its generated workspace without launching and start Metro separately. Replace <App> with the workspace/scheme name in ios/ and <device> with the simulator UUID:
xcodebuild -workspace ios/<App>.xcworkspace -scheme <App> -configuration Debug \
-destination 'platform=iOS Simulator,id=<device>' -derivedDataPath ./build
npx expo start --dev-client --localhostWith Metro running, use another terminal to install build/Build/Products/Debug-iphonesimulator/<App>.app with xcrun simctl install <device> <path>. Open the development server once with xcrun simctl openurl <device> '<app scheme>://expo-development-client/?url=http%3A%2F%2F127.0.0.1%3A8081', substituting the scheme from the built app’s Info.plist. Then use the setup prompt’s launch and log commands. Use a simulator UUID when more than one is booted.