Authentication issues
The authentication popup is blocked or doesn't open
The authentication popup is blocked or doesn't open
socketfi.authenticate() does nothing, or the browser shows a “popup blocked” notification.Cause: Browsers block popups that aren’t triggered by a direct user interaction (a click or keypress). If authenticate() is called inside a useEffect, a timeout, or any async chain that started without a user gesture, the browser will block it.Solution: Always call authenticate() directly from an event handler, never from lifecycle hooks or timers:localhost.INVALID_CLIENT_ID error on authenticate()
INVALID_CLIENT_ID error on authenticate()
authenticate() throws an error with code INVALID_CLIENT_ID.Cause: The clientId passed to new SocketFi({ clientId }) doesn’t match a registered application in SocketFi.Solution: Verify that:- Your environment variable (
VITE_SOCKETFI_CLIENT_ID) is correctly set and not empty. - The client ID in your SocketFi project dashboard exactly matches what your app is reading.
- You’re using the right environment variable prefix for your bundler (
VITE_for Vite,NEXT_PUBLIC_for Next.js). - You’re not accidentally using a TESTNET client ID against MAINNET or vice versa.
Passkey prompt doesn't appear — authentication stalls
Passkey prompt doesn't appear — authentication stalls
- Confirm the user is on a supported browser with WebAuthn enabled.
- Check that the page is served over HTTPS (or
localhost). Passkeys require a secure context. - Test in a standard (non-private) browser window. Some browsers restrict passkeys in private/incognito mode.
- On mobile, ensure the device has at least one passkey or biometric method enrolled.
USER_CANCELLED is thrown — is something broken?
USER_CANCELLED is thrown — is something broken?
authenticate() or requestTransaction() throws an error with code USER_CANCELLED.Cause: The user deliberately closed the authentication or approval screen.Solution: This is expected behaviour, not an application error. Handle it silently or with a neutral prompt:Session issues
Session is lost after a page refresh
Session is lost after a page refresh
useState) only, so it’s cleared on unmount.Solution: Persist the session to localStorage after a successful authenticate() call, and restore it on app startup inside AuthProvider:Session becomes null unexpectedly mid-session
Session becomes null unexpectedly mid-session
setSession(null) is called unintentionally (check logout logic), or a component re-mount resets state without checking localStorage first.Solution:- Audit the
AuthProviderto ensure the startupuseEffectonly runs once (empty dependency array[]). - Make sure your logout function is only called from explicit user action, never from an error handler or lifecycle side effect.
- If using React StrictMode (which runs effects twice in development), confirm your session restore logic is idempotent.
Token expired — users are getting 401 errors
Token expired — users are getting 401 errors
401 Unauthorized from your backend, even though they haven’t logged out.Cause: The socketfiAccessToken has a limited lifetime. When it expires, verifyAuth() on your backend will reject it.Solution: Detect 401 responses in your API layer and trigger reauthentication:Transaction issues
TRANSACTION_REJECTED — how should I handle it?
TRANSACTION_REJECTED — how should I handle it?
requestTransaction() throws TRANSACTION_REJECTED after the approval screen is shown.Cause: The user tapped or clicked “Reject” on the hosted approval screen.Solution: Treat this as a normal user action. Show a neutral message, re-enable the submit button, and preserve the form values so the user can try again:POLICY_VIOLATION — transaction blocked by wallet policy
POLICY_VIOLATION — transaction blocked by wallet policy
requestTransaction() throws POLICY_VIOLATION.Cause: A spending policy configured on the user’s smart wallet blocked the transaction — for example, a per-transaction spending limit was exceeded.Solution:- Surface a clear explanation to the user: “This transaction exceeds your wallet’s spending limit.”
- Check your application’s wallet policy configuration in the SocketFi dashboard.
- If appropriate, suggest a smaller transaction amount.
Transaction appears to be stuck — no result and no error
Transaction appears to be stuck — no result and no error
requestTransaction() but the promise never resolves or rejects. The UI is frozen in a loading state.Cause: Usually a popup blocker preventing the approval screen from opening, or a network connectivity issue preventing the SDK from communicating with SocketFi services.Solution:- Check the browser console for errors.
- Ensure
requestTransaction()is called from a direct user interaction (click handler), not from a timer or effect. - Implement a timeout in your UI — if the transaction hasn’t settled after a reasonable period, show an error and re-enable the form.
- Check network connectivity and verify the SocketFi service status.
Transaction confirmed but balance doesn't update
Transaction confirmed but balance doesn't update
requestTransaction() resolves successfully with a transactionHash, but the displayed balance still shows the old value.Cause: Cached query data from readContract() hasn’t been invalidated yet.Solution: After a confirmed transaction, invalidate the relevant React Query cache keys:Backend / verifyAuth issues
verifyAuth() returns invalid — getting 401 on every request
verifyAuth() returns invalid — getting 401 on every request
401 Unauthorized, even with a freshly obtained token.Cause: A few common causes:- The
Authorizationheader is missing or malformed. - The frontend is not attaching the token correctly.
- The token has expired.
- You’re passing the entire session object instead of just the
socketfiAccessTokenstring.
session.socketfiAccessToken (the string), not the whole session object.INVALID_TOKEN error from verifyAuth()
INVALID_TOKEN error from verifyAuth()
verifyAuth() throws or returns valid: false with an INVALID_TOKEN reason.Cause: The token value is corrupted, was generated for a different environment (e.g. TESTNET token used against a MAINNET project), or has been tampered with.Solution:- Confirm the token is being read directly from
session.socketfiAccessTokenwithout modification. - Make sure your frontend and backend are configured for the same network (
TESTNETorMAINNET). - Try signing out, signing back in to get a fresh token, and retrying.
TOKEN_EXPIRED error from verifyAuth()
TOKEN_EXPIRED error from verifyAuth()
verifyAuth() returns valid: false with a TOKEN_EXPIRED reason, causing backend 401 responses for users who were previously signed in.Cause: The socketfiAccessToken has a limited lifetime. After expiry, verifyAuth() rejects it.Solution: Detect 401 responses in your API layer and redirect the user to re-authenticate:useFetch hook) so you handle token expiry consistently across your whole app.INVALID_SIGNATURE error from verifyAuth()
INVALID_SIGNATURE error from verifyAuth()
verifyAuth() returns valid: false with an INVALID_SIGNATURE reason even for tokens that appear correct.Cause: The token’s cryptographic signature could not be verified. This usually means the token was issued by a different project or environment, or the token value was altered in transit or storage.Solution:- Confirm you’re not accidentally modifying the token string (e.g. trimming, encoding, or base64-wrapping it) before passing it to
verifyAuth(). - Ensure the token is read verbatim from
session.socketfiAccessToken. - Confirm your frontend and backend target the same SocketFi environment (TESTNET vs MAINNET).
- If the problem persists, sign out and back in to obtain a fresh, correctly-signed token.
POPUP_BLOCKED — authentication popup prevented by the browser
POPUP_BLOCKED — authentication popup prevented by the browser
authenticate() or requestTransaction() throws POPUP_BLOCKED, or the browser silently blocks the hosted flow without opening it.Cause: The browser’s popup blocker prevented the hosted flow from opening because it wasn’t triggered directly by a user gesture.Solution: Always call authenticate() and requestTransaction() from a synchronous click or keyboard event handler — never from a useEffect, setTimeout, or async chain that started without a user gesture:req.user is undefined in route handlers
req.user is undefined in route handlers
req.user is undefined even though you added the auth middleware.Cause: Either the middleware isn’t applied to that route, or it isn’t being awaited properly.Solution:- Confirm
authMiddlewareis applied before the route handler in your Express setup. - Make sure the middleware calls
next()on success (not just setsreq.user). - Check that
authMiddlewareis declared asasyncand thatawait verifyAuth(token)is awaited. - Ensure your TypeScript
express.d.tsdeclaration file is included in yourtsconfig.jsonsoreq.userhas the right type.
verifyAuth() behaves unexpectedly in tests — stale key cache
verifyAuth() behaves unexpectedly in tests — stale key cache
@socketfi/server caches public keys between calls to reduce network overhead. In test environments, this cache can carry state across test runs and produce unexpected results.Solution: Call clearKeyCache() in a beforeEach or afterAll hook to reset the cache between tests:React Native issues
Authentication completes but the app doesn't reopen — deep link not triggering
Authentication completes but the app doesn't reopen — deep link not triggering
returnTo URL passed to the SDK doesn’t match your registered app scheme.Solution:- Define a custom URL scheme for your app (e.g.
socketpay://) inapp.json(Expo) or your native build config. - Pass the scheme as the
returnTooption when callingauthenticate(). - For Expo, ensure
expo-linkingis installed and your scheme is registered underexpo.schemeinapp.json. - Test deep links independently using
adb shell am start(Android) orxcrun simctl openurl(iOS) before testing the full flow.
Authentication gets stuck in the browser on React Native
Authentication gets stuck in the browser on React Native
app.json and what was passed to the SDK.Solution:- For Expo managed workflow: verify
app.jsonincludes"scheme": "yourappscheme"and rebuild withexpo prebuild. - For bare React Native: verify the scheme is registered in
AndroidManifest.xmlandInfo.plist. - Confirm the
returnTovalue exactly matches the registered scheme including the://separator.
Session not persisting between app launches on React Native
Session not persisting between app launches on React Native
useState) rather than persistent secure storage. Memory state is lost when the app process ends.Solution: Use expo-secure-store (or an equivalent) instead of localStorage: