Shopify Managed Installation: What's New?
For years, every Shopify app developer knew the drill: a merchant clicks install, Shopify redirects to your /auth/callback, and that is where your app came alive — you created the shop record, stored the access token, registered webhooks, seeded defaults. The callback was the birthplace of every installation.
Shopify managed installation removes that callback entirely. Nothing calls your backend when a merchant installs your app. So how are installations detected now?
Before going any further, let’s take a look at what a Shopify-managed installation is and then we will circle back and anwser the question above.
What managed installation is
With the legacy authorization code grant, your app drove the installation: it decided which scopes to request at runtime, redirected the merchant to the grant screen, verified HMACs and nonces, and exchanged an authorization code for an access token.
With managed installation, Shopify drives much of the process now. You declare your required scopes statically in shopify.app.toml:
[access_scopes]scopes = "read_products,write_customers"optional_scopes = ["read_discounts", "write_products"] # requested later, merchant may declineShopify handles installation and permission updates on every shop. No redirects, no grant-screen round-trips, no OAuth security boilerplate in your codebase. Embedded apps then acquire access tokens through token exchange — swapping the session token the frontend already holds for an API token, server-side, with zero browser navigation.
The proposed advantages are faster installs, no screen flicker, no iframe-escape gymnastics, and an entire class of OAuth implementation bugs deleted from your app. Scope changes become a config redeploy instead of a per-shop OAuth ceremony. Ultimately, this is suposed to bring better experience for the end-user.
The main issue for app developers now is that the apps are not being notified that an installation has occurred.
The detection problem
After managed installation:
- There is no OAuth callback — the flow that used to hit
/auth/callbackis no more. - There is no
app/installedwebhook topic.
So how does your backend ever learn a shop installed the app?
Right after installation, Shopify launches your embedded app in the admin. App Bridge hands your frontend a session token — a one-minute JWT whose dest claim identifies the shop. Every request from your frontend to your backend carries it. Your backend validates the JWT and asks one question:
Do I have a record (and a working token) for this shop?
If not — congratulations, you just detected an installation.
Where initialization goes
The old callback did three jobs: detect app installs, get a token, register webhooks Here’s where each one lives now.
1. App Installation Detection
Detectin happens by analyzing the sessions token that your front-end holds. The most straighforward approach is to make a bootstrapping call on every app load to your backend (something like “/api/init”) which will check if the session token belongs to an app that you are already tracking or not. Depending on the result, you can either trigger your bootstrapping logic or do nothihg.
2. Access Token Acquisition
Many apps require access token to perform work in the background without user interaction. Previously apps used a never-expiring access token but new apps are now obliged to use the new expiring token mechanism. Old apps will be required to switch to the new mechanism in 2027. Feel free to check our my article on the expiring token mechanics here.
3. Automatic Webhook Subscription
Webhook subscriptions can now live inside the app configuration file so that app are automatically subscribed for certain webhooks. So we now have App-specific (TOML) subscriptions which apply uniformly to every shop that installs the app and also Shop-specific which are created at runtime via the GraphQL Admin API, and their configuration can differ per shop — that’s the whole reason they exist. The docs’ rule of thumb: choose app-specific unless your topics, delivery URIs, or filters need to vary between shops.
Here is how to do that in TOML app configuration file:
[webhooks]api_version = "2026-10"
[[webhooks.subscriptions]]topics = ["app/uninstalled"]uri = "/webhooks/app/uninstalled"
[[webhooks.subscriptions]]topics = ["app/scopes_update"]uri = "/webhooks/app/scopes_update"
# Multiple webhooks per endpoint[[webhooks.subscriptions]]topics = ["products/create", "products/update", "products/delete"]uri = "/webhooks/products"shopify app deploy applies these to every shop, forever. Bonus: config-managed subscriptions are retained by Shopify even when deliveries fail, unlike API-created ones, which get deleted after persistent failures.
Do declare app/scopes_update and app/uninstalled. The former fires on the managed-install grant itself which covers the theoretical edge case of a shop that installs but never opens the app. The latter is your teardown signal.
The checklist
If you’re migrating an app (or building a new one), the callback-era logic
maps like this:
| Old home (auth callback) | New home |
|---|---|
| Detect install | First validated session token for an unknown shop using a init andpoint |
| Create shop record | Handled by the init endpoint |
| Acquire token | Token exchange in the same init path |
| Register webhooks | shopify.app.toml + shopify app deploy |
| Seed data / defaults | Handled by the init workflow |
| Detect uninstall | app/uninstalled webhook |
| Scope-change handling | See the companion post — it’s far more interesting than the docs suggest |
The mental shift is the hard part: installation is no longer an event your app handles; it’s a state your app discovers. Once the upsert-on-auth pattern is in place, everything else follows.