Skip to main content
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'"
Image
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:
Content-Security-Policy: frame-src 'self' *.meshconnect.com;
If you set CSP via an HTML meta tag:
<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.
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.
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:
NetworknetworkId
Ethereume3c7fdd8-b1fc-4e51-85ae-bb276e075611
Solana0291810a-5947-424d-9a59-e88bb33e999d
Baseaa883b03-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.
// 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);
});
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.

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.
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.

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.
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.

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). Human readers can safely ignore this.llms.txt — Common errorsTroubleshooting 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.