
Security News
Open VSX Unblocks Extension IDs Used in Malware Campaign
Open VSX has removed three extension IDs from its malicious-extension list as the legitimate publishers they impersonated move to claim the names for themselves.
@circlehq/push-react-native
Advanced tools
CircleHQ Push React Native SDK — register device push tokens, identify users, and report notification events.
CircleHQ Push SDK for React Native. Register device tokens, identify users, display notifications, and track engagement events — all backed by Circle's infrastructure. No Firebase project setup required on your end.
| Peer dependency | Version |
|---|---|
react-native | >=0.71.0 |
@react-native-async-storage/async-storage | >=1.19.0 |
@react-native-firebase/app | >=20.0.0 |
@react-native-firebase/messaging | >=20.0.0 |
@notifee/react-native | >=7.0.0 (optional but strongly recommended) |
@expo/config-plugins | >=7.0.0 (Expo only) |
@react-native-firebase/* and @notifee/react-native are optional at runtime — the SDK degrades gracefully if they are absent, but push notifications will not function without them.
npm install @circlehq/push-react-native
# or
yarn add @circlehq/push-react-native
Install required peer dependencies:
npm install @react-native-async-storage/async-storage \
@react-native-firebase/app \
@react-native-firebase/messaging \
@notifee/react-native
Before using the SDK, upload your Firebase Service Account JSON on the Circle dashboard (Settings → Push Notifications). Circle stores it and uses it to hand back your platform-specific config on demand — you never put Firebase credentials in your app's source code.
Add the config plugin to your app.json or app.config.ts with your Circle API key:
{
"plugins": [
["@circlehq/push-react-native/plugin", {
"circleApiKey": "YOUR_CIRCLE_API_KEY",
"androidNotificationIcon": "./assets/notification_icon.png"
}]
]
}
At expo prebuild time, the plugin fetches your config from Circle's API and:
google-services.json, applies the Google Services Gradle plugin, adds POST_NOTIFICATIONS permissionGoogleService-Info.plist, adds it to Xcode resources, calls FirebaseApp.configure() in AppDelegate, sets UIBackgroundModes and aps-environmentThen run prebuild:
npx expo prebuild
Run the setup script with your Circle API key from your project root — it fetches google-services.json and GoogleService-Info.plist from Circle's API and writes them into android/app/ and ios/<ProjectName>/:
npx circle-push-setup --api-key YOUR_CIRCLE_API_KEY
The script prints the remaining manual steps (Gradle plugin, AppDelegate, Xcode resource). Then follow the React Native Firebase setup guide and the notifee installation guide for the rest of the native configuration.
This import must be the very first line of your app entry file, before any React component or other import. It registers native background handlers synchronously at module evaluation time. If it runs too late, background push events will be silently dropped.
// index.js — FIRST LINE
import '@circlehq/push-react-native/background';
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('MyApp', () => App);
For Expo Router, add it as the first line of app/_layout.tsx:
// app/_layout.tsx — FIRST LINE
import '@circlehq/push-react-native/background';
import { Stack } from 'expo-router';
// ...
import CirclePush from '@circlehq/push-react-native';
// 1. Initialize (call once, e.g. in App.tsx useEffect)
await CirclePush.init({
apiKey: 'cir_live_xxxxxxxxxxxx',
});
// 2. Identify a user — triggers permission prompt and device registration
const device = await CirclePush.identify({
email: 'user@example.com',
});
console.log(device.deviceId, device.pushToken);
CirclePush.init(config)Initialize the SDK. Must be called and resolved before any other method.
await CirclePush.init({
apiKey: 'cir_live_xxxxxxxxxxxx', // required
debug: __DEV__, // verbose logging, default: false
});
Safe to call multiple times with the same config — subsequent calls are no-ops. Throws CirclePushConfigError if called with a different config after initialization.
Config options:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Circle External API key. Required. |
apiBaseUrl | string | https://api.circlehq.co | Override the Circle API base URL |
debug | boolean | false | Enable verbose console logging |
autoRequestPermission | boolean | true | Auto-prompt for permission on identify() when status is default |
androidNotificationIcon | string | 'notification_icon' | Android small icon resource name from res/drawable/ |
androidChannelId | string | 'circle-push-default' | Android notification channel ID |
CirclePush.identify(identity)Register the current user's device. Handles the full flow: permission request → FCM token fetch → backend registration.
const device = await CirclePush.identify({
email: 'user@example.com', // at least one of email or phone is required
phone: '+12125551234',
firstName: 'Jane',
lastName: 'Doe',
});
// device: { deviceId, contactId, pushToken, platform, isValid }
CirclePushPermissionError if notifications are denied or unsupported.CirclePush.requestPermission()Explicitly prompt the user for notification permission. Useful when autoRequestPermission is false.
const status = await CirclePush.requestPermission();
// 'granted' | 'denied' | 'default' | 'unsupported'
CirclePush.getPermissionState()Returns the last known permission state synchronously (no async call).
const status = CirclePush.getPermissionState();
CirclePush.getToken()Returns the stored FCM push token, or null if not yet obtained.
const token = await CirclePush.getToken();
CirclePush.refresh()Re-fetches the FCM token and re-registers with the backend. Useful after a long session to ensure the token is current.
await CirclePush.refresh();
Returns null if the SDK has no identity or permission is not granted.
CirclePush.unregister()Unregisters the device from the backend, deletes the local FCM token, and clears stored state.
await CirclePush.unregister();
CirclePush.on(event, callback)Subscribe to SDK events. Returns an unsubscribe function.
const unsub = CirclePush.on('notificationClicked', ({ messageId, link, action }) => {
if (link) router.push(link);
});
// Later:
unsub();
Available events:
| Event | Payload | Description |
|---|---|---|
notificationReceived | NotificationPayload | A push notification arrived while the app is in the foreground |
notificationClicked | NotificationClickPayload | User tapped a notification or action button |
notificationDismissed | NotificationDismissedPayload | User dismissed a notification |
permissionChange | PermissionState | Notification permission status changed |
tokenRefresh | { oldToken, newToken } | FCM token was rotated |
error | CirclePushError | A non-fatal internal error occurred |
CirclePush.setDebug(enabled)Toggle verbose logging at runtime.
CirclePush.setDebug(true);
interface NotificationPayload {
title?: string;
body?: string;
image?: string;
data?: Record<string, string>;
actions?: NotificationAction[];
}
interface NotificationClickPayload {
messageId?: string;
campaignId?: string;
link?: string;
action?: string; // action button id, if an action button was tapped
}
interface NotificationDismissedPayload {
messageId?: string;
campaignId?: string;
}
All errors thrown from public methods are instances of CirclePushError with a stable code for programmatic branching:
import {
CirclePushError,
CirclePushPermissionError,
CirclePushApiError,
} from '@circlehq/push-react-native';
try {
await CirclePush.identify({ email: 'user@example.com' });
} catch (e) {
if (e instanceof CirclePushPermissionError) {
// e.code === 'permission/denied' | 'permission/unsupported'
showPermissionPrompt();
} else if (e instanceof CirclePushApiError) {
// e.status is the HTTP status code
console.error('API error', e.status, e.message);
} else if (e instanceof CirclePushError) {
console.error(e.code, e.message, e.retryable);
}
}
Error classes:
| Class | Codes |
|---|---|
CirclePushConfigError | config/missing_api_key, config/already_initialized |
CirclePushValidationError | validation/missing_identity, validation/invalid_email, validation/invalid_phone, validation/invalid_token |
CirclePushPermissionError | permission/denied, permission/unsupported |
CirclePushTokenError | token/fetch_failed, token/delete_failed |
CirclePushApiError | api/4xx, api/5xx, api/network, api/rate_limited |
CirclePushError | runtime/not_initialized, runtime/unknown |
// index.js
import '@circlehq/push-react-native/background'; // MUST BE FIRST
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('MyApp', () => App);
// App.tsx
import { useEffect } from 'react';
import CirclePush, { CirclePushPermissionError } from '@circlehq/push-react-native';
export default function App() {
useEffect(() => {
async function setup() {
await CirclePush.init({
apiKey: 'cir_live_xxxxxxxxxxxx',
debug: __DEV__,
});
try {
await CirclePush.identify({ email: 'user@example.com' });
} catch (e) {
if (e instanceof CirclePushPermissionError) {
console.warn('Notifications not permitted');
}
}
}
setup();
const unsub = CirclePush.on('notificationClicked', ({ link, messageId }) => {
console.log('Notification tapped:', messageId, link);
});
return () => unsub();
}, []);
return <YourApp />;
}
The SDK ships full TypeScript declarations. All public types are exported from the main entry:
import type {
CirclePushConfig,
Identity,
RegisteredDevice,
PermissionState,
NotificationPayload,
NotificationClickPayload,
NotificationDismissedPayload,
EventMap,
EventName,
Platform,
} from '@circlehq/push-react-native';
MIT
FAQs
CircleHQ Push React Native SDK — register device push tokens, identify users, and report notification events.
We found that @circlehq/push-react-native demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.

Security News
Open VSX has removed three extension IDs from its malicious-extension list as the legitimate publishers they impersonated move to claim the names for themselves.

Product
Socket’s PHP and Composer support is now in Beta for all customers, with PHP reachability analysis generally available.

Product
Socket is bringing experimental protection to Firefox, scanning 97,000+ extensions in Mozilla's official directory for malware and risky updates.