Building a public Shopify application

Did you know?

This tutorial describes how to build a Shopify application in Ruby. The concepts presented here will also apply to developers building applications in other languages such as Python, Node.js and PHP.

After you have generated an API key and password for your public Shopify application you can start the actual development process.

For this tutorial, you’ll be building your application in Ruby with the official Shopify API gem and the Sinatra framework for Ruby apps.

The sample code for this example app can be found in this GitHub repository. The example app showcases how to subscribe to webhook notifications and update product inventory across multiple product variants for a gift basket product.


To follow this tutorial, you will need the following:

  • Ruby (this example was written with Ruby 2.2.1)

  • RubyGems, the package management tool for Ruby

  • Bundler for Ruby, a dependency manager

  • Download the example code from the GitHub repository

  • To obtain the necessary dependencies, navigate to the application folder and type bundle install

Step 1: Exposing your application to the Internet

Your application is going to be receiving requests from Shopify, so it needs to be exposed to the Internet. The simplest way to achieve this is through a tunneling service such as ngrok, which will allow you to create a secure tunnel from the public Internet to your local machine.

To initialize ngrok, you’ll need to do the following:

  1. Download ngrok.
  2. In terminal, navigate to the folder where you downloaded ngrok.
  3. If you're working on OSX or Linux, start ngrok with ./ngrok http 4567. If you're on Windows, start ngrok with ngrok http 4567.

This will create a tunnel to your local machine on port 4567, which is the default port for Sinatra applications.

Step 2: Configuring your application

Each time you start ngrok, you’ll be assigned a randomly generated subdomain (e.g. You will need to configure your application to refer to this address so that Shopify knows where to find your app:

  1. In the sample app code folder, open app.rb and find the initialize method.

  2. Change the @app_url parameter so that it matches the subdomain that you’ve been assigned by ngrok. If you're not using ngrok, this parameter should match the URL where your application is deployed.

  3. In the folder containing the project code, create a new file named .env and open it in a text editor.

    Did you know?

    This example application uses the dotenv Ruby gem to allow you to pull your @key and @secret credentials in from an external file. Your .env file is ignored in GitHub by your .gitignore file, so your credentials will not be visible to humans.

  4. Log in to your Shopify Partner account.

  5. Click Apps then click the name of your app (or Create app).

Your API Key is displayed, and you can click Show secret beside Credential sets to retrieve your API Secret:

Example app 1

       6. Copy the values of the API Key and API Secret from your dashboard, and add them to the .env file in the following format:


       7. Save the .env file and close it.

Configure App URLs

You will also need to configure your application’s URLs.

In the partner dashboard:

  1. Click Apps.

  2. Click the name of your app.

  3. Click Edit app settings.

  4. For the Application URL, type https://<@app_url>/giftbasket/install where @app_url is the same value that you defined inside of your initialize method.

  5. For Redirection URL, type https://<@app_url>/giftbasket/auth.

Step 3: Running your app locally

After configuring your app, you can run the example code locally:

  1. Open a new terminal window.

  2. Navigate into the example code folder.

  3. Type ruby app.rb.

Install your app on a test store

You can also install your app on a test store to test its behavior in the Shopify admin.

  1. Log into your Partner's Dashboard in a new tab.

  2. Click Apps.

  3. Click the name of the new app you've created.

  4. Click Edit App Store Listing.

  5. Click Save.

  6. Click View app listing. The listing page for your app loads in the Shopify App Store.

  7. Click Get to install the app to your test store.

Step 4: Building your application

This section of the tutorial will examine the application code contained in app.rb and explain the rationale for each block of code.


To start, some useful libraries are included for developing your application:

require 'shopify_api'
require 'sinatra'
require 'httparty'
require 'dotenv'

The Shopify API gem allows you to effortlessly make API calls to Shopify. Sinatra is a lightweight web framework for Ruby that you’ll use to quickly develop your web application. You’ll also be using HTTParty to perform HTTP requests. As mentioned previously, the dotenv gem will allow you to load environment variables from an external configuration file.


Examine the initialize method for the GiftBasket class:

class GiftBasket < Sinatra::Base

  def initialize
    @key = ENV['API_KEY']
    @secret = ENV['API_SECRET']
    @app_url = ""
    @tokens = {}
  • The initialize method is called every time the application is started.

  • The dotenv gem is invoked in order to import the parameters defined in the .env file that you created.

  • Some instance variables (@key, @secret, @app_url) are defined to store the application's API key and secret key, as well as the base URL of the application.

  • The empty hash is declared (@tokens), which will be used to store the access tokens granted to us by Shopify.

Did you know?

In a production environment, you would likely want to use a more permanent method of storage for access tokens such as a relational database.


Sinatra uses routes to invoke a particular method when a client sends an HTTP request to the specified address.

get '/giftbasket/install' do
  shop = request.params['shop']
  scopes = "read_orders,read_products,write_products"

  # construct the installation URL and redirect the merchant
  install_url =

  redirect install_url

The first route defined in the application, /giftbasket/install, is used to define the address where the merchant is redirected to when they click Get in the Shopify App Store. This is the address that you defined earlier as the Application URL in the application settings of your partner dashboard.

When the merchant hits this route, you need to redirect them to the application installation screen on Shopify which will look like this:

Build public app 3

In the parameters of the install_url, you need to include the following:

  • The API key of the application.

  • Permission scopes required for the application (in this case, the application needs permission to read orders, read products, and write products).

  • The redirect_uri parameter, which is where the merchant will be redirected after they authorize the installation. This should match the URL defined in the partner dashboard as the Redirection URL. In the case, the merchant is redirected to /giftbasket/auth.

Authenticating with Shopify

After the merchant has authorized the installation of your application, you’ll need to authenticate with Shopify using the OAuth protocol. If you’re not familiar with OAuth, please see our documentation on OAuth.

Verify the request

The first step is to verify that the request is indeed coming from Shopify. You will be able to verify this by performing HMAC signature validation.

get '/giftbasket/auth' do
  # extract shop data from request parameters
  shop = request.params['shop']
  code = request.params['code']
  hmac = request.params['hmac']

  # perform hmac validation to determine if the request is coming from Shopify
  h = request.params.reject{|k,_| k == 'hmac' || k == 'signature'}
  query = URI.escape(h.sort.collect{|k,v| "#{k}=#{v}"}.join('&'))
  digest = OpenSSL::HMAC.hexdigest('sha256'), @secret, query)

  if not (hmac == digest)
    return [403, "Authentication failed. Digest provided was: #{digest}"]

First, the hmac and signature parameters are removed from the hash. Next, the keys in the hash are sorted lexicographically, and each key and value pair is joined with an ampersand (‘&’). The resulting string is hashed with the SHA256 algorithm using the application’s secret key as the encryption key. The resulting digest is compared against the hmac parameter provided in the request. If the comparison is successful, you can be assured that this is a legitimate request from Shopify.

Get an access token

In order to perform Shopify API calls on a particular shop, you'll need an access token belonging to that shop.

if @tokens[shop].nil?
  url = "https://#{shop}/admin/oauth/access_token"

  payload = {
    client_id: @key,
    client_secret: @secret,
    code: code}

  response =, body: payload)

To obtain the access token, send a POST request to https://<shop>/admin/oauth/access_token where shop is the domain of the shop where the application is being installed (e.g. The body of the POST request will contain the API key for the application, the application secret key, as well as the code provided in the original request parameters.

If the request was formed correctly, you should expect to receive a response with status code 200 (OK). This response will contain the access token that you’ll be able to use to instantiate a session with the particular Shopify store that you are trying to access. For the sake of this example, you’ll be storing your access token in a hash where the key is the shop domain.

  if response.code == 200
    @tokens[shop] = response['access_token']
    return [500, "Something went wrong."]

Did you know?

For the sake of this example, we’ve written our own simple OAuth strategy in order to illustrate the process. If you’re building a Ruby application outside of this example, you should use the omniauth-shopify-oauth2 Ruby gem.

Creating the webhook subscription

After receiving the access token, you can use it to instantiate a session with Shopify. Once the session is active, your application can begin making API calls to Shopify.

This example application application utilizes Webhooks which allow Shopify to communicate with the application when certain shop events are triggered.

session =, @tokens[shop])

if not ShopifyAPI::Webhook.find(:all).any?
  webhook = {
    topic: 'orders/create',
    address: "https://#{@app_url}/giftbasket/webhook/order_create",
    format: 'json'}


In this case, the webhook subscription has the topic orders/create. This means that Shopify will send a POST request to the specified address every time a new order is created on the merchant's shop.

Receiving and verifying webhooks

When your application receives the POST request to the address you've specified, the first thing you need to do is verify that the request is actually from Shopify and not a potential attacker. The HTTP header of every webhook request sent from Shopify contains a HMAC-SHA256 signature generated using the application’s secret key and the data contained in the body of the request. You will need to generate the same signature and compare it against the header value.

post '/giftbasket/webhook/order_create' do
    # inspect hmac value in header and verify webhook
    hmac = request.env['HTTP_X_SHOPIFY_HMAC_SHA256']

    data =
    webhook_ok = verify_webhook(hmac, data)


Inside the body of the verify_webhook helper function, the SHA256 hash is calculated and compared against the header value. A value of true or false is returned.

def verify_webhook(hmac, data)
  digest ='sha256')
  calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, @secret, data)).strip

  hmac == calculated_hmac

If the comparison was successful, you'll need to extract the shop name from the request header and look up the access token necessary to activate a session with Shopify.

if webhook_ok
  shop = request.env['HTTP_X_SHOPIFY_SHOP_DOMAIN']
  token = @tokens[shop]

  if not token.nil?
    session =, token)
    return [403, "You're not authorized to perform this action."]
  return [403, "You're not authorized to perform this action."]

The body of the request will contain the information about the new order in the form of a JSON-encoded Order resource.

See example JSON output ›

Making API calls

When the webhook request is received, the application performs the following actions:

  1. Inspects the line_items property of each order.
  2. Inspects the variant_id property of each line item and then determines if they contain a metafield property with the key ingredients.
  3. If true, the app considers that product variant to be a gift basket. The value of the metafield contains a comma-separated list of variant_ids that belong to the gift basket product (created by the merchant).
  4. Decrements the inventory quantity for each of those product variants.
json_data = JSON.parse data

line_items = json_data['line_items']

line_items.each do |line_item|
  variant_id = line_item['variant_id']

  variant = ShopifyAPI::Variant.find(variant_id)

  variant.metafields.each do |field|
    if field.key == 'ingredients'
      items = field.value.split(',')

      items.each do |item|
        gift_item = ShopifyAPI::Variant.find(item)
        gift_item.inventory_quantity = gift_item.inventory_quantity - 1

return [200, "Webhook notification received successfully."]

Did you know?

In a production application you will need to observe the API call limit. We suggest implementing a queueing mechanism for your API calls.

Going forward

Keep in mind that this tutorial was built solely for the purpose of introducing developers to Shopify's API. If you're building apps in languages other than Ruby, you can apply the same concepts to build your own public Shopify app.

If you're interested in developing a production-quality application for Shopify, you can take a look at one of the following libraries hosted on GitHub:

These libraries contains some useful helper functions for developing your application as well as built-in configuration that makes it easy to deploy your application.

App review process by Shopify

Before you apply to have your app published in the Shopify App store, be sure to follow the guidelines in the app review checklist. This is the same checklist that Shopify's Apps team will use when considering publishing your app.