Embedded app check failing despite App Bridge + session token implementation (Vercel serverless)

Hi everyone,

I’m building a Shopify embedded app (E-Rechnung / E-Invoice app for the German market) hosted on Vercel as serverless functions (no Remix/Next.js, pure HTML responses). The app works correctly inside Shopify Admin, but the automated embedded app checks keep failing during app review.

What I’ve implemented

  1. App Bridge meta tag + CDN script on every page:
<meta name="shopify-api-key" content="MY_API_KEY">
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>

  1. Session token retrieval via shopify.idToken() on page load:
shopify.ready().then(function() {
  shopify.idToken().then(function(token) {
    // Use token for authenticated API calls
    loadDashboardData();
  });
});

  1. Server-side JWT verification of session tokens (HS256, validating aud, iss, dest, exp claims)

  2. window.fetch() used explicitly (not a saved reference) so App Bridge can intercept and inject tokens automatically

  3. CSP headers set correctly:

Content-Security-Policy: frame-ancestors https://*.myshopify.com https://admin.shopify.com;

  1. CORS headers on API endpoints to allow App Bridge cross-origin requests

  2. OAuth flow uses window.open(authUrl, '_top') to properly break out of the iframe

My setup

  • Hosting: Vercel (serverless functions returning HTML, no SPA framework)

  • App URL: https://e-rechnung-app.clever-invoice.com

  • Routing: Vercel rewrites route /app/* to /api/shopify-app/app/*

  • Auth: Flexible approach - session token (preferred) > HMAC > shop param fallback

What I’ve tried

  • Verified App Bridge loads and shopify global is available

  • Added explicit shopify.idToken() calls to generate Shopify telemetry

  • Ensured every page renders App Bridge before any JavaScript executes

  • Added App Bridge readiness polling with 5-second retry timeout

  • All API endpoints accept and verify Bearer tokens from session tokens

Questions

  1. Does anyone have experience with serverless-only apps (no Remix/Node app) passing the embedded app checks? Is there something specific the checker bot expects that the Remix template handles automatically?

  2. Does the checker bot visit a specific URL path (like / or /app), and does it need to see a specific response pattern?

  3. Is there a way to debug what the checker bot actually tests? The failure message is generic and doesn’t tell me which specific check failed.

  4. Could the issue be related to timing - the checker bot might not wait long enough for App Bridge to initialize on a cold-start serverless function?

Any help or insights from developers who’ve passed this check with a non-Remix setup would be greatly appreciated!

Thanks,
Uwe

Hi @Uwe_Peukert

The automated embedded app checker works by making a plain HTTP request (like curl) to the App URL configured in your Partners Dashboard and parsing the raw HTML response. It doesn’t execute JavaScript — so the <meta name="shopify-api-key"> tag and the <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script> tag must be present in the server-rendered HTML.

The most common reason this fails with serverless setups is that the endpoint redirects unauthenticated requests (e.g., to an OAuth flow) instead of returning the page directly. Run curl -s https://e-rechnung-app.clever-invoice.com/app | head -50 and confirm both tags are in the output with a 200 status — if the checker gets a redirect or a challenge page, it’ll never see App Bridge.

Your OAuth approach using window.open(authUrl, '_top') suggests you’re using the authorization code grant flow, which introduces redirects that can trip up the checker. The recommended approach for embedded apps is now token exchange combined with Shopify managed installation — your app always renders the page with App Bridge first, then uses shopify.idToken() to get a session token, and your backend exchanges that for an API access token. No redirects needed, and the checker sees a clean HTML page with App Bridge on first load every time.

For your serverless setup specifically, also check whether Vercel’s bot protection or any edge middleware is intercepting the checker’s request before it reaches your function. Other developers behind Cloudflare have hit this exact issue (related thread). Try temporarily disabling any protection on your /app routes and see if the check passes. Let me know what the curl output looks like and we can go from there!

Hi Liam, I ran the curl test and both tags are present with 200 status:

curl -s https://e-rechnung-app.clever-invoice.com/app | head -10

Shows <meta name="shopify-api-key" content="..."> and <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"> correctly. No redirects, no bot protection. CSP headers set. Session tokens used via shopify.idToken(). Can you trigger a manual re-check or tell me specifically what the checker is seeing?