When the app is initially installed subscribe to appuninstalled webhook. When the app is uninstalled Shopify will send you a request signifying the uninstall action. Prior to this you can assume the app is installed. Without an access token you can't send valid requests to Shopify API so your choices are limited.
In that case write some application logic that updates a timestamp on your database when the customer logs in so you know they are active. Or you can subscribe to the appuninstall webhook the next time they use your app like after authentication and then all your existing installations will be fine. The ones in the past that never subscribed I think will be somewhat a hack to do this. Or you can simply email your merchants and ask if they're still using it, just a thought.
As recommended by the documentation, Apps are supposed to suppose to account for missed webhooks. For this reason I have a nightly task to "catch up" with anything that might have possibly been missed.
But, that task will fail for Shops that are no longer active. This includes Shops that have lapsed subscriptions (see https://community.shopify.com/c/shopify-apis-and-sdks/402-quot-errors-quot-quot-unavailable-shop-quo...)
I would love to know if there is an API endpoint where we can query to see whether API requests will succeed or fail. Otherwise I'm stuck doing something like
raise(error) unless error.message.include?("Unavailable Shop")