Once you have a basic app installed you’ll want to setup webhooks. These allow your application to receive event notices from Shopify.

The first event we want to handle is the app/uninstalled so we know when the application has been uninstalled.

Start by creating a migration

bundle exec rails g migration add_uninstalled_at_to_shops

Edit db/migrate/xxxxxxxxxxxxxx_add_uninstalled_at_to_shops.rb and add the uninstalled_at column.

  def change
    add_column :shops, :uninstalled_at, :datetime, null: true, after: :shopify_token
  end

Then run the migrations.

bundle exec rails db:migrate
bundle exec rails db:migrate RAILS_ENV=test

I like to add an active scope to the Shop model (app/models/shop.rb) so it’s easy to find just the active shops.

  scope :active, -> { where(uninstalled_at: nil) }

Edit config/initializers/shopify_app.rb and add inside ShopifyApp.configure

  config.webhooks = [
    {topic: 'app/uninstalled', address: "#{ENV['SHOPIFY_WEBHOOKS_BASE_URL']}/app_uninstalled"}
  ]

You’ll want to download https://ngrok.com/ and start it. This will give you an external URL that will forward to your local development server.

~/Downloads/ngrok http 3000
                                                                        

Edit start.sh and add with the URL ngrok provided.

export SHOPIFY_WEBHOOKS_BASE_URL=http://8c582705.ngrok.io/webhooks

Note: If you’re using the freee version of ngrok you’ll get a new URL every time so you’ll need to keep updating start.sh. Also, any old webhooks that are installed will go the wrong URL and will now fail

Now create a Rails job called AppUninstalled.

bundle exec rails g job AppUninstalled

Edit app/jobs/app_uninstalled_job.rb so that it sets the uninstalled_at attribute for the shop. I also change the shop_domain with the suffix -uninstalled-#{shop_id}. This way the next time the app is installed it will get a clean installation but you will keep the old installation just in case the store owner made a mistake or you want to see how the application was configure at the time it was uninstalled.

class AppUninstalledJob < ApplicationJob
  def perform(*webhooks)
    # Just in case we don't get an array
    return unless webhooks.is_a?(Array)

    webhooks.each do |webhook|
      # Try the next one if we don't have a shop_domain
      next unless webhook.key?(:shop_domain)

      # Find the shop and try the next one if we can't find it
      shop = Shop.active.find_by(shopify_domain: webhook[:shop_domain])
      next if shop == nil

      # This allows us to track the uninstall and keep the old data just in case we need to restore it
      shop.update(
        shopify_domain: "#{shop.shopify_domain}-uninstalled-#{shop.id}",
        uninstalled_at: DateTime.now)
    end
  end
end

Note: it’s important that you remove the queue_as :default if you are going to have multiple apps running on a single server

Edit config/environments/development.rb and change active job to run inline to make debugging easier in development

  # Use inline for development only
  config.active_job.queue_adapter = :inline

Now start the development server.

./start.sh

Remove the application, if it’s currently installed, then install it. This will cause the webhook to be installed for the store. If you then uninstall the app again and check the ngrok console you should see the webhook called.

You can also check the store was uninstalled

Now commit everything to version control.

git add app/models app/jobs spec/jobs db config/environments/development.rb config/initializers/shopify_app.rb
git commit -m "Added app_uninstall job to handle the store being uninstalled"
git push

END OF PART 2!