Handle Shopify Mandatory Webhooks in Ruby on Rails

writing

Since May 25th, 2018 GDPR has imposed obligations on any party that collects, stores, or processes personal data. App developers have the responsibility to comply with these regulations. Fortunately, Shopify has implemented endpoints to help app developers deal with data privacy to meet the requirements of GDPR.

Read about Shopify API GDPR requirements.

Mandatory Webhooks

Mandatory webhooks differ slightly from regular webhooks from Shopify. If you’re using the shopify_app gem you can use the webhooks manager to configure your apps needed webhooks. Unfortunately this only works for the regular Shopify admin API events, the mandatory webhooks have to be set up manually. This doesn’t mean the app gem can’t help us out, we’ll still use it for verifying webhooks, more on that later.

Read about getting started with Shopify webhooks.

Mandatory Webhooks Controller

Create a new controller called MandatoryWebhooksController. This controller will have three methods, an endpoint for each mandatory webhook.

Create (app/controllers/mandatory_webhooks_controller.rb):

class MandatoryWebhooksController < ApplicationController
  
  def shop_redact
  end

  def customer_redact
  end

  def customer_data_request
  end
  
end

Create a private method that only excepts only the params we want and returns them. The except method returns a hash with everything except for the given values.

private
  def webhook_params
    params.except(:controller, :action, :type)
  end

Now we can use webhook_params in our controller methods to access the data sent by the webhook request.

Webhook Verification

Webhooks need to be verified before being processed to be sure that the request came from shopify and not someone else.

In the header of the request, Shopify includes an HMAC. This is the SHA256 digest calculated from the body of the request and your apps shared secret. To verify the webhook you have to calculate this HMAC and compare it to the one in the request header, if they match then it is a valid webhook from Shopify.

Shopify’s getting started with webhooks guide has a good section on how to manually verify webhooks.

Luckily the shopify_app gem can handle this for us, no need to worry about calculating and compare the HMAC. To do this include ShopifyApp::WebhookVerification at the start of the controller class.

class MandatoryWebhooksController < ApplicationController
  include ShopifyApp::WebhookVerification
  ...
end

Complete the endpoint methods

Start by setting the params permitted attribute to true, then we can use webhook_params to do whatever we want with the webhook data.

Then we need to return a response to Shopify to say everything is okay. Mandatory webhooks need to respond with a 200 series status code. Calling head :no_content will respond with a "204 no content" status. We aren’t sending any data back to Shopify with these webhooks so 204 just indicates that the webhook was received and there’s nothing to send back.

def shop_redact
  params.permit!
  # do something
  head :no_content
end

Before head is called we can do whatever we want with the webhook data. We can log a ticket in your support system or send yourself an email with data.

Routes

Now that the webhooks can be processed we need to set up routes that Shopify can post to when a webhook is fired. We can make the endpoint locations be whatever we want, then map them to the appropriate controller methods.

controller :mandatory_webhooks do
  post '/webhooks/shop_redact' => :shop_redact
  post '/webhooks/customers_redact' => :customers_redact
  post '/webhooks/customers_data_request' => :customers_data_request
end

We then have to go into our apps set up in the partners dashboard and find the mandatory webhooks section. Here we specify the URLs for each webhook endpoint.

Shopify app set up mandatory webhooks

Shopify app set up mandatory webhooks

Shopify can now send a mandatory webhook to our app and we will verify it and respond accordingly.

Jobs

One thing we might want to do is process the webhooks using ActiveJob instead of doing everything inline in the controller.

Create a job for each webhook like this (app/jobs/shop_redact_job.rb):

class ShopRedactJob < ActiveJob::Base
  def perform(shop_domain:, webhook:)
    shop = Shop.find_by(shopify_domain: shop_domain)

    shop.with_shopify_session do
      # redact information related to this shop
    end
  end
end

In this job, we can do all the processing we want like creating support tickets, deleting database information, sending emails, etc.

Lastly, we need to queue the job when the webhook is received. Modify the controller methods for each endpoint like this:

def shop_redact
  params.permit!
  ShopRedactJob.perform_later(shop_domain: shop_domain, webhook: webhook_params.to_h)
  head :no_content
end

Now ActiveJob will handle the heavy lifting of the webhook and we don’t have to worry about having the main app do extra processing.