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
- 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>
- 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();
});
});
-
Server-side JWT verification of session tokens (HS256, validating
aud,iss,dest,expclaims) -
window.fetch()used explicitly (not a saved reference) so App Bridge can intercept and inject tokens automatically -
CSP headers set correctly:
Content-Security-Policy: frame-ancestors https://*.myshopify.com https://admin.shopify.com;
-
CORS headers on API endpoints to allow App Bridge cross-origin requests
-
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
shopifyglobal 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
-
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?
-
Does the checker bot visit a specific URL path (like
/or/app), and does it need to see a specific response pattern? -
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.
-
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