> ## Documentation Index
> Fetch the complete documentation index at: https://docs.socket.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# SocketFi Integration Troubleshooting and Debugging Guide

> Diagnose and fix common SocketFi integration issues — auth failures, session problems, transaction errors, backend 401s, and React Native deep links.

This guide covers the most common issues developers encounter when integrating SocketFi, organized by category. Each issue follows a **Symptom → Cause → Solution** format so you can identify the problem quickly and apply the right fix. If your issue isn't listed here, check the browser console and network tab first — most problems surface a clear error code or HTTP status.

<Note>
  Before troubleshooting, collect your SDK version, the exact error message or code, the environment (TESTNET vs MAINNET), and the steps to reproduce. This information makes every problem easier to diagnose.
</Note>

## Authentication issues

<Accordion title="The authentication popup is blocked or doesn't open">
  **Symptom:** Calling `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:

  ```typescript theme={null}
  // ✓ Correct — called from a click handler
  const handleLogin = async () => {
    await socketfi.authenticate();
  };

  // ✗ Incorrect — called from useEffect
  useEffect(() => {
    socketfi.authenticate(); // browser will block this
  }, []);
  ```

  Also check that your browser settings allow popups for your domain, and that you're testing on HTTPS or `localhost`.
</Accordion>

<Accordion title="INVALID_CLIENT_ID error on authenticate()">
  **Symptom:** `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.

  ```bash theme={null}
  # Check the value in your shell during dev
  echo $VITE_SOCKETFI_CLIENT_ID
  ```
</Accordion>

<Accordion title="Passkey prompt doesn't appear — authentication stalls">
  **Symptom:** The SocketFi authentication popup opens but hangs without showing a passkey prompt, or closes immediately.

  **Cause:** The user's device or browser doesn't support WebAuthn / passkeys, or the browser is in a state (e.g. private window on some browsers) that restricts passkey access.

  **Solution:**

  * Confirm the user is on a [supported browser](/resources/supported-platforms) 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.
</Accordion>

<Accordion title="USER_CANCELLED is thrown — is something broken?">
  **Symptom:** `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:

  ```typescript theme={null}
  try {
    await socketfi.authenticate();
  } catch (err) {
    if (err.code === "USER_CANCELLED") {
      // User closed the dialog — no action needed, or show "Try again"
      return;
    }
    // All other errors are real failures
    setError("Authentication failed. Please try again.");
  }
  ```
</Accordion>

***

## Session issues

<Accordion title="Session is lost after a page refresh">
  **Symptom:** The user is signed in, refreshes the page, and is redirected to the login screen.

  **Cause:** The session is being held in component state (`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`:

  ```typescript theme={null}
  // On login
  const result = await socketfi.authenticate();
  localStorage.setItem("socketfi_session", JSON.stringify(result));

  // On startup (useEffect in AuthProvider)
  const stored = localStorage.getItem("socketfi_session");
  if (stored) setSession(JSON.parse(stored));
  ```

  See the full implementation in the [React Authentication example](/examples/react-authentication).
</Accordion>

<Accordion title="Session becomes null unexpectedly mid-session">
  **Symptom:** The user is logged in, navigates within the app, and is suddenly treated as unauthenticated.

  **Cause:** Usually a state management issue — either `setSession(null)` is called unintentionally (check logout logic), or a component re-mount resets state without checking `localStorage` first.

  **Solution:**

  * Audit the `AuthProvider` to ensure the startup `useEffect` only 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.
</Accordion>

<Accordion title="Token expired — users are getting 401 errors">
  **Symptom:** Users who were signed in are getting `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:

  ```typescript theme={null}
  const response = await fetch("/api/me", { headers: authHeaders });
  if (response.status === 401) {
    // Token expired — ask the user to sign in again
    logout();
    navigate("/login");
  }
  ```

  Consider implementing an Axios/fetch interceptor to handle this globally rather than per-request.
</Accordion>

***

## Transaction issues

<Accordion title="TRANSACTION_REJECTED — how should I handle it?">
  **Symptom:** `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:

  ```typescript theme={null}
  } catch (err) {
    if (err.code === "TRANSACTION_REJECTED" || err.code === "USER_CANCELLED") {
      setStatus({ type: "failed", message: "Transaction cancelled." });
      return; // Don't clear the form — let the user retry
    }
    setStatus({ type: "failed", message: err.message });
  }
  ```
</Accordion>

<Accordion title="POLICY_VIOLATION — transaction blocked by wallet policy">
  **Symptom:** `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.
</Accordion>

<Accordion title="Transaction appears to be stuck — no result and no error">
  **Symptom:** You called `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.
</Accordion>

<Accordion title="Transaction confirmed but balance doesn't update">
  **Symptom:** `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:

  ```typescript theme={null}
  const queryClient = useQueryClient();

  const result = await transactionService.sendTokens(...);
  if (result.success) {
    queryClient.invalidateQueries({ queryKey: ["balance", walletAddress] });
  }
  ```
</Accordion>

***

## Backend / verifyAuth issues

<Accordion title="verifyAuth() returns invalid — getting 401 on every request">
  **Symptom:** Every authenticated API request returns `401 Unauthorized`, even with a freshly obtained token.

  **Cause:** A few common causes:

  * The `Authorization` header 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 `socketfiAccessToken` string.

  **Solution:** Verify the request header format:

  ```http theme={null}
  Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
  ```

  And verify what you're extracting on the server:

  ```typescript theme={null}
  const token = req.headers.authorization?.replace("Bearer ", "");
  // token should be a JWT string, not an object
  const result = await verifyAuth(token);
  ```

  Double-check that you're reading `session.socketfiAccessToken` (the string), not the whole `session` object.
</Accordion>

<Accordion title="INVALID_TOKEN error from verifyAuth()">
  **Symptom:** `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.socketfiAccessToken` without modification.
  * Make sure your frontend and backend are configured for the same network (`TESTNET` or `MAINNET`).
  * Try signing out, signing back in to get a fresh token, and retrying.
</Accordion>

<Accordion title="TOKEN_EXPIRED error from verifyAuth()">
  **Symptom:** `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:

  ```typescript theme={null}
  const response = await fetch("/api/me", { headers: authHeaders });
  if (response.status === 401) {
    logout();
    navigate("/login");
  }
  ```

  Consider a global fetch interceptor (Axios or a custom `useFetch` hook) so you handle token expiry consistently across your whole app.
</Accordion>

<Accordion title="INVALID_SIGNATURE error from verifyAuth()">
  **Symptom:** `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.
</Accordion>

<Accordion title="POPUP_BLOCKED — authentication popup prevented by the browser">
  **Symptom:** `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:

  ```typescript theme={null}
  // ✓ Correct — called directly from a click handler
  <button onClick={() => socketfi.authenticate()}>Sign in</button>

  // ✗ Incorrect — browser will block this
  useEffect(() => { socketfi.authenticate(); }, []);
  ```
</Accordion>

<Accordion title="req.user is undefined in route handlers">
  **Symptom:** Inside a route handler, `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 `authMiddleware` is applied before the route handler in your Express setup.
  * Make sure the middleware calls `next()` on success (not just sets `req.user`).
  * Check that `authMiddleware` is declared as `async` and that `await verifyAuth(token)` is awaited.
  * Ensure your TypeScript `express.d.ts` declaration file is included in your `tsconfig.json` so `req.user` has the right type.

  ```typescript theme={null}
  // app.ts
  app.use("/api", authMiddleware, profileRoutes); // ✓ middleware before routes
  ```
</Accordion>

<Accordion title="verifyAuth() behaves unexpectedly in tests — stale key cache">
  **Symptom:** Token verification passes or fails inconsistently across test cases, even though the tokens are correct.

  **Cause:** `@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:

  ```typescript theme={null}
  import { clearKeyCache } from "@socketfi/server";

  afterEach(() => {
    clearKeyCache();
  });
  ```

  This is only needed in test environments. In production, the cache refreshes automatically.
</Accordion>

***

## React Native issues

<Accordion title="Authentication completes but the app doesn't reopen — deep link not triggering">
  **Symptom:** The SocketFi authentication flow opens in the system browser and completes, but the user is left in the browser and the app doesn't reopen.

  **Cause:** Deep linking isn't configured for your app scheme, or the `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://`) in `app.json` (Expo) or your native build config.
  * Pass the scheme as the `returnTo` option when calling `authenticate()`.
  * For Expo, ensure `expo-linking` is installed and your scheme is registered under `expo.scheme` in `app.json`.
  * Test deep links independently using `adb shell am start` (Android) or `xcrun simctl openurl` (iOS) before testing the full flow.
</Accordion>

<Accordion title="Authentication gets stuck in the browser on React Native">
  **Symptom:** The authentication flow opens in a browser or WebView but never returns to the app, even though the scheme appears to be configured.

  **Cause:** The URL scheme isn't registered in the platform-level build configuration, or there's a mismatch between the scheme in `app.json` and what was passed to the SDK.

  **Solution:**

  * For Expo managed workflow: verify `app.json` includes `"scheme": "yourappscheme"` and rebuild with `expo prebuild`.
  * For bare React Native: verify the scheme is registered in `AndroidManifest.xml` and `Info.plist`.
  * Confirm the `returnTo` value exactly matches the registered scheme including the `://` separator.
</Accordion>

<Accordion title="Session not persisting between app launches on React Native">
  **Symptom:** Users must sign in every time they launch the app on React Native / Expo.

  **Cause:** The session is being stored in memory (`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`:

  ```typescript theme={null}
  import * as SecureStore from "expo-secure-store";

  // Save
  await SecureStore.setItemAsync("socketfi_session", JSON.stringify(session));

  // Restore on startup
  const stored = await SecureStore.getItemAsync("socketfi_session");
  if (stored) setSession(JSON.parse(stored));
  ```
</Accordion>

***

## Debugging checklist

When you're stuck, work through this list before escalating:

```text theme={null}
✓ Is the SDK initialized with the correct clientId?
✓ Are environment variables defined and non-empty?
✓ Is the app served over HTTPS (or localhost)?
✓ Is authenticate() / requestTransaction() called from a user gesture?
✓ Is the session being persisted to localStorage / SecureStore?
✓ Is the correct network configured (TESTNET vs MAINNET)?
✓ Is the contract ID correct for the selected network?
✓ Is the method name spelled correctly (check ABI)?
✓ Are the arguments in the right order and type?
✓ Is the Bearer token being extracted correctly on the backend?
✓ Is verifyAuth() being awaited?
✓ Is authMiddleware applied before route handlers?
✓ Are deep link schemes configured for React Native?
```
