Troubleshooting
Symptom-driven debugging for @motisig/expo-motisig-sdk.
Symptoms grouped by area. Set EXPO_PUBLIC_DEBUG=1 (or your equivalent log gate) and tail the Metro console — every MotiSig* error throws through the public promise, so a missing await ... .catch(...) will silently drop failures.
| Symptom | Likely cause | Fix |
|---|
initialize returns false | sdkKey or projectId was empty (after trimming). | Pass non-empty strings; verify EXPO_PUBLIC_* env vars are exposed to the bundle. |
Subsequent initialize calls do nothing | The instance is already initialized. | Construct a new MotiSig() after reset() if you really need to re-initialize. |
| HTTP requests go to the wrong host | baseURL not passed and resolveClientBaseUrl fell back to the default. | Pass baseURL explicitly, or set the override your resolveClientBaseUrl reads from. |
401 / 403 on every request | Wrong sdkKey for the target environment. | Confirm key matches the project. Each request sends X-API-Key and X-Project-ID. |
| Symptom | Likely cause | Fix |
|---|
getExpoPushToken() returns null on simulator | expo-device reports isDevice === false. | Test on a physical device. |
getExpoPushToken() returns null on device | EAS project id missing. | Add extra.eas.projectId in app.json (or pass easProjectId to initialize). The included app.plugin.js warns at prebuild time. |
| Push token never refreshes | Your app revoked notification permission. | Re-grant in Settings; the SDK syncs permission on the next foreground transition. |
| Push subscription not removed on logout | No token was ever obtained for this user. | Without a token, there's nothing to remove server-side. |
| Symptom | Likely cause | Fix |
|---|
Error('No user is set; call setUser first') | A user-scoped method was called before setUser. | await motisig.setUser(...) before any mutation. |
MotiSigApiError with 409 on setUser | Server already has the user id. | Expected — the SDK swallows this and persists locally. If you see a thrown 409, inspect the rest of the stack; the swallow is in setUser only. |
| Mutations resolve in unexpected order | Two awaits started concurrently from different code paths. | The AsyncQueue serializes by enqueue order. If you start two promises in parallel without await, the queue still serializes them — but the order is whichever run() was called first. Await sequentially when order matters. |
| Symptom | Likely cause | Fix |
|---|
addListener fires nothing | skipNotificationListeners: true was passed to initialize. | Re-init without that flag, or call the lower-level MotiSigEmitter if you want HTTP-only mode. |
notification_response not firing on cold start | Notifications.getLastNotificationResponseAsync() returned null (no pending tap), or the host app reset the response cache. | Confirm with await Notifications.getLastNotificationResponseAsync() from your own code; the SDK calls it once during initialize. |
wasForeground always false | App was killed between receiving the notification and the user tapping it; the foreground id ring buffer doesn't survive process death. | Expected. Use wasForeground only as a hint, not as authoritative state. |
| Foreground banner never shows | Notifications.setNotificationHandler not called. | Add it before App renders. See Getting started. |
| Click tracking never fires | Payload missing messageId, no user set, or the listener is suppressing via data.suppressForeground. | Verify payload, ensure setUser ran (or soon after — queued clicks flush on setUser), check the data shape. |
| Click tracking lost after kill/relaunch | AsyncStorage not installed; queue was in-memory only. | Add @react-native-async-storage/async-storage to the host app so clicks and user id persist across relaunches. |
| Duplicate clicks in analytics | Unstable messageId or manual trackClick plus automatic tracking. | SDK dedupes by messageId per install; use one stable id per notification. |
See Rich notification images for the full checklist. Most common failures:
- Missing
expo-notification-service-extension-plugin in app.json.
- Wrong
mode (development vs production) for the build channel.
- Image URL not HTTPS or returns
403/404.
Notifications.setNotificationHandler missing → no foreground banner.
- Trying to test in Expo Go, which does not include your NSE.
| Symptom | Likely cause | Fix |
|---|
pnpm install complains about peer deps | The SDK declares expo, expo-notifications, expo-constants, expo-device, react-native as peers. | Add the missing ones to your app's dependencies. |
require('./client/MotiSig') errors at import | Your bundler tree-shook the Proxy shim. | Import from the package root (import { MotiSig } from '@motisig/expo-motisig-sdk'), not from a deep path. |
expo prebuild --clean deleted my NSE | The NSE source lives under ios/ and was overwritten. | Move NotificationService.m to a project-root folder (./notification-service/) and point the plugin at it. |