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

# Common errors

This page documents the most common errors developers run into when integrating with Mesh — drawn from real support cases and developer feedback. Each entry covers the symptom, root cause, and fix.

## SDK domain not authorized

**Symptom:** The Mesh SDK loads but immediately shows an authorization error, or the connection fails silently. May surface as a CORS error in the browser console.

**Root cause:** Mesh validates that the SDK is loaded from an approved domain. Any domain not on your allowlist — including `localhost` — is rejected. This most commonly affects developers testing locally or on a staging URL they forgot to add.

**Fix:** In the Mesh dashboard, go to **Account > API keys > Access** and add every domain where your app runs:

* `localhost:3000` (or your local port)
* Any staging or preview domains
* Your production domain(s)

Changes take effect within a few minutes.

## SDK iframe blocked by Content Security Policy

**Symptom:** The Mesh Link modal never appears — the area where Link should render shows as a blank grey box. The browser console shows a CSP error such as: `Refused to frame 'https://web.meshconnect.com/' because an ancestor violates the following Content Security Policy directive: "frame-src 'self'"`

<Frame>
  <img src="https://mintcdn.com/mesh-40/Y-QnDCJ7fcNNqJ5o/images/image-18.png?fit=max&auto=format&n=Y-QnDCJ7fcNNqJ5o&q=85&s=42fc7b5c35e0f3fd4a3e2b941de5a201" alt="Image" title="Image" className="mx-auto" style={{ width:"25%" }} width="444" height="729" data-path="images/image-18.png" />
</Frame>

**Root cause:** Your app's Content Security Policy doesn't permit Mesh's hosted iframe to load.

**Fix:** Add `*.meshconnect.com` to your CSP `frame-src` directive:

```text theme={null}
Content-Security-Policy: frame-src 'self' *.meshconnect.com;
```

If you set CSP via an HTML meta tag:

```html theme={null}
<meta http-equiv="Content-Security-Policy"
      content="frame-src 'self' *.meshconnect.com;">
```

## Ad-blocker preventing OAuth connection

**Symptom:** After selecting an OAuth-based integration (eg. Coinbase, Gemini), the user gets stuck on a loading spinner and never completes authentication. The Link UI may appear initially but the OAuth flow never resolves.

**Root cause:** Ad-blocking software can interfere with Link's ability to use browser storage and complete OAuth redirect flows. This includes browser extensions (uBlock Origin, AdBlock Plus, etc.) and browsers with built-in ad-blocking — most commonly Brave.

**Fix:** The user should disable their ad-blocker for your domain, or switch to a standard browser without built-in ad-blocking. Ad-blocking software is not officially supported with Mesh Link.

<Info>
  If this surfaces frequently in support, consider adding a prompt in your UI that detects ad-blocker presence and suggests disabling it before launching Link.
</Info>

## Link Token expired or already used

**Symptom:** `openLink()` throws an error or the user immediately sees an error screen when Link opens. The error typically references an invalid or expired token.

**Root cause:** Link Tokens are **short-lived (10 minutes) and single-use**. A token fetched at page load may expire before the user clicks the connect button. A token from a previous session can never be reused.

**Fix:**

* Fetch a fresh Link Token immediately before calling `openLink()` — on the button click, not on page load.
* After each session (successful or failed), discard the token. Never cache or reuse tokens.

## Network or symbol not supported / empty catalog

**Symptom:** Link opens but the integration catalog is empty, or Link returns an error on the `toAddresses` fields.

**Root cause (most common):** An ERC-20 contract address (eg. `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) was passed as the `symbol` field instead of the token ticker (eg. `USDC`). Mesh expects the **token symbol**, not the contract address.

**Other causes:**

* A `networkId`/`symbol` combination that doesn't exist in Mesh's catalog.
* A misspelled token symbol.

**Fix:** Use the ticker symbol for `symbol`. Key network IDs for reference:

| Network  | networkId                              |
| -------- | -------------------------------------- |
| Ethereum | `e3c7fdd8-b1fc-4e51-85ae-bb276e075611` |
| Solana   | `0291810a-5947-424d-9a59-e88bb33e999d` |
| Base     | `aa883b03-120d-477c-a588-37c2afd3ca71` |

To maximize coverage, include multiple `networkId`/`symbol` pairs in `transferOptions.toAddresses` — eg. USDC on Ethereum, Solana, and Base.

## No eligible assets after account connection

**Symptom:** The user connects their exchange account but sees a "No eligible assets" screen. The `transferNoEligibleAssets` SDK event fires.

**Root cause:** The user's connected account doesn't hold any tokens matching the `networkId`/`symbol` pairs in your `toAddresses` array. This is expected behavior when there's a genuine mismatch — not a bug — but it needs to be handled gracefully.

**Common causes:**

* The user holds ETH, but you only accept USDC.
* The user holds USDC on Ethereum, but `toAddresses` only includes USDC on Solana.
* The user genuinely has a zero balance.
* In sandbox: accidentally using the `Mesh2` test account, which has an empty portfolio.

**Fix:**

* Broaden your `toAddresses` array to include more asset/network pairs.
* Listen for `transferNoEligibleAssets` and use its `arrayOfTokensHeld` payload to show a helpful message in your UX (eg. "You don't have USDC in that account. Try a different account or deposit manually.").
* In sandbox: use the `Mesh` test account (full portfolio) for general testing. Use `Mesh2` specifically to test your empty-state UI.

## Webhook signature validation fails

**Symptom:** Your webhook handler rejects every incoming event with a signature mismatch, even though your webhook secret looks correct.

**Root cause:** The HMAC-SHA256 signature in Mesh's `X-Mesh-Signature-256` header is computed over the **raw request body bytes** — exactly as received off the wire. The most common mistake is JSON re-serializing the body before computing the HMAC. Parsing the body and then re-serializing it (eg. calling `JSON.stringify(JSON.parse(body))`) can silently change key ordering, whitespace, or number formatting — producing a different byte sequence and therefore a wrong HMAC.

**Fix:** Capture the **raw body bytes** before any parsing, compute the HMAC over those raw bytes, then parse the JSON afterward.

```javascript theme={null}
// Node.js / Express — correct approach
const crypto = require('crypto');

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-mesh-signature-256'];
  const hmac = crypto
    .createHmac('sha256', process.env.MESH_WEBHOOK_SECRET)
    .update(req.body) // req.body is a raw Buffer — do NOT re-serialize
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hmac))) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body); // parse AFTER verification
  // handle event...
  res.sendStatus(200);
});
```

<Warning>
  **The key mistake:** using `express.json()` middleware on the webhook route. This parses the body into a JS object before you can capture the raw bytes, making correct HMAC verification impossible. Use `express.raw({ type: 'application/json' })` instead, scoped to the webhook route only.
</Warning>

## Transfer stays `pending` or finalizes as `failed`

**Symptom:** A transfer is initiated but the `succeeded` webhook never arrives, or arrives as `failed`.

**Common causes and fixes:**

* **Exchange-side delay**: CEX transfers can take minutes to hours to post on-chain. The `pending` webhook fires when the transfer is initiated at the exchange; `succeeded` fires once it confirms on-chain. For Coinbase and Binance, this can occasionally take up to 24 hours. This is normal — build your flow to wait for `succeeded` before crediting the user.
* **Amount below exchange minimum**: Most exchanges enforce a minimum withdrawal. If the amount is too small, the exchange rejects it. Use `minAmount` / `minAmountInFiat` in your `toAddresses` to surface minimums before the user initiates.
* **Incorrect destination address format**: Some exchange/network combinations enforce strict address validation. Verify that destination addresses are valid for the specified network.
* **User 2FA timeout**: If the user took too long to approve the MFA step, the exchange session may have expired and the transfer was never sent.
* **Sandbox limitation**: In sandbox, the `pending` webhook is not guaranteed — transfers may skip directly to `succeeded` or `failed`. Don't build logic that depends on receiving `pending` during testing.

For transfers stuck in `pending` in production, contact your Mesh representative with the `TransferId` from the webhook payload.

## OAuth integrations show blank screen or raw code in mobile WebView

**Symptom:** When launching an OAuth-based integration (eg. Uphold, Paribu) from inside a native app's in-app browser, the user sees a blank white screen or a page of raw code instead of the provider's login screen. The OAuth flow never completes.

**Root cause:** Mesh Link's OAuth flows open the provider's authorization URL in a new window via `window.open()`. Native in-app browsers — iOS `WKWebView`, Android `WebView`, and React Native / Flutter equivalents — block or silently drop `window.open()` calls by default unless the host app explicitly implements a popup handler. The two symptoms reflect slightly different failure modes:

* **Blank screen:** The WebView opens an empty window but never navigates to the OAuth URL — the popup is created but the navigation is discarded.
* **Raw code screen:** The OAuth callback with an authorization code lands in the wrong context and renders as a plain page rather than being intercepted by the SDK.

**Fix:** The host app needs to handle `window.open()` at the native layer:

* **iOS (`WKWebView`):** Implement the `WKUIDelegate` method `webView(_:createWebViewWith:for:windowFeatures:)` to catch popup navigations and load them in a new `WKWebView` or `SFSafariViewController`.
* **Android (`WebView`):** Override `WebChromeClient.onCreateWindow()` to handle new-window requests.
* **React Native:** Use `react-native-inappbrowser-reborn` or open OAuth URLs via the device browser (`Linking.openURL`) rather than inside a WebView.

Alternatively, if the host app cannot be modified, ask your Mesh representative about redirect-based OAuth configuration, where the callback is handled via a deep link / custom URL scheme instead of a popup postMessage.

<Info>
  This is a common limitation of WebView environments. If your product is a mobile app, test OAuth integrations inside the actual in-app browser early — behavior can differ significantly from a standard desktop or mobile browser.
</Info>

## Duplicate webhook deliveries

**Symptom:** Your webhook handler receives the same event multiple times, causing duplicate processing (eg. crediting a user twice).

**Root cause:** Mesh delivers webhooks with at-least-once semantics. If your server returns a non-2xx response or takes too long to respond, Mesh retries the delivery.

**Fix:** Use the `EventId` field as an idempotency key. Store every processed `EventId` and skip any event you've already handled. Return `200 OK` quickly (before heavy processing), then handle the event asynchronously.

<Info>
  **`EventId` vs `Id`**: The `Id` field changes on each delivery attempt. The `EventId` stays constant for the same logical event across all retries. Always deduplicate on `EventId`.
</Info>

***

<Accordion title="AI coding reference (llms.txt)">
  *AI coding reference — a compact summary of this page's APIs, parameters, and patterns for use by AI coding assistants (following the [llms.txt standard](https://llmstxt.org/)). Human readers can safely ignore this.*

  **llms.txt — Common errors**

  Troubleshooting guide for the most common Mesh integration errors.

  **SDK domain not authorized** (CORS error / auth error on load): Dashboard > Account > API keys > Access → add all domains including `localhost:3000`.

  **SDK iframe blocked by CSP** (blank grey box): Add `*.meshconnect.com` to `frame-src` CSP directive.

  **Ad-blocker interfering with OAuth** (spinner, OAuth never resolves): User must disable ad-blocker or use standard browser. Brave and uBlock Origin are common culprits.

  **Link Token expired or already used** (error on openLink()): Fetch new token on button click (not page load). Tokens are 10 min, single-use. Never cache or reuse.

  **Empty catalog / symbol not supported**: Use ticker symbol (e.g. `USDC`), not ERC-20 contract address. Include multiple networkId/symbol pairs.

  **Webhook signature validation fails**: Header is `X-Mesh-Signature-256`. Compute HMAC-SHA256 over **raw request body bytes** before any parsing. Use `express.raw()` not `express.json()` in Node.js.

  **Duplicate webhook deliveries**: Use `EventId` as idempotency key. Return `200` immediately, process async.

  **OAuth blank screen / raw code in mobile WebView**: Implement `window.open()` handler at native layer: iOS → `WKUIDelegate`, Android → `WebChromeClient.onCreateWindow()`. Or `Linking.openURL` in React Native.

  **Transfer stuck pending / finalizes as failed**: CEX delay is normal (up to 24h); check `minAmount` in toAddresses; verify destination address format; sandbox `pending` webhook not guaranteed.
</Accordion>
