3.Develop your application service

After you have entered the relevant information as per the aforementioned process, before proceeding with testing and submitting for review, you need to develop your application service. It interacts with Shoplazza through a series of APIs.

Obtain Authorization for Shoplazza API

OverView

Public applications use OAuth 2.0 for authorization, and the interaction process is shown in the figure below.

Detail Steps Info

Step 2: The Shoplazza backend establishes communication and authorization through the APP URL.

After Merchant add app from APP Store,the shoplazza services will send a requet by APP URL which configed by partner center.

For example:
https://stamped-review.apps.shoplazza.com/api/auth?hmac=c4caf9b08bdeff7531bb12712ffea860264ec24f5fd953832505c5024d19edca&install_from=app_store&shop=rwerwre.myshoplaza.com&store_id=1339409

The formatter is:
app_url?hmac=${hmac}&install_from=app_store&shop=${store_domian_name}&store_id=${store_id}

Step3: Security Checks by APP Service

Before we continue, make sure your app performs the following security checks. If any of the checks faill, then your app must reject the request with an error, and must not continue.

  • The hmac is valid and signed by Shoplazza
  • The shop parameter is a valid shop hostname, ends with myshoplaza.com
    For Security Checks, SHOPLAZZA OAuth SDKs also has corresponding methods, you can quickly verify hmac and shop parameter by SHOPLAZZA OAuth SDKs, for instance: Oauth-SDK-Go, see example below:
import (
  co "github.com/shoplazza-os/oauth-sdk-go"
  "github.com/shoplazza-os/oauth-sdk-go/shoplazza"
)

oauth := &co.Config{
                ClientID:     "s1Ip1WxpoEAHtPPzGiP2rK2Az-P07Nie7V97hRKigl4",
                ClientSecret: "0LFJcNqVb2Z1nVt9xT72vOo0sTWd6j8wVX60Y5xdzZZ",
                Endpoint:     shoplazza.Endpoint,
                RedirectURI:  "https://3830-43-230-206-233.ngrok.io/oauth_sdk/redirect_uri/",
                Scopes:       []string{"read_shop"},
        }
oauth.ValidShop("xxx.myshoplaza.com")        // verify shop parameter

var requestUrl = "http://example.com/some/redirect_uri?code={authorization_code}&shop={store_name}.myshoplaza.com&hmac={hmac}"
query := strings.Split(requestUrl, "?")
params, _ := url.ParseQuery(query[1])
oauth.SignatureValid(params)                 // verify hmac

HMac Validation Detail

Every request or redirect from Shoplazza to your app's server includes an hmac parameter that can be used to verify the authenticity of Shoplazza. For each reqeust, you must remove the hmac entry from the query string and process it through an HMAC-SHA256 hash function.
For example, for following request:

http://example.com/some/redirect/uri?code=1vtke5ljOOL2jPds6gM0TNCeYZDitYB&shop=simon.myshoplaza.com&hmac=22bad22eee1f92836f7773e87d973479

To remove the hmac, you can transform the query string to a map, remove the hmac key-value pair, and then lexicographically concatenate your map back to a query string. This leaves the remaining parameters from the example query string:

code=1vtke5ljOOL2jPds6gM0TNCeYZDitYB&shop=simon.myshoplaza.com
Process the hash function

After you remove hmac and reformat the query string, you can process the string through an HMAC-SHA256 hash function using the Client secret Shoplazza provided to your app. The message is authenticated if the generated hexdigest is equal to the value of the hmac parameer.
The following Ruby example show how to process the string through a hash function:

def verified_hmac?(hmac)
  sha256 = OpenSSL::Digest::SHA256.new
  query_string = "code=1vtke5ljOOL2jPds6gM0TNCeYZDitYB&shop=simon.myshoplaza.com"
  calculated_hmac = OpenSSL::HMAC.hexdigest(sha256, CLIENT_SECRET, query_string)
  ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac)
end

HMac Validation by Node.js

In this tutorial, we will walk through the creation of a simple Shoplazza OAuth app that retrieves a list of customers. We will also validate the incoming request using HMAC (Hash-based Message Authentication Code) to ensure it's genuinely coming from Shoplazza.
Prerequisites:

  • Node.js installed.
  • Basic knowledge of Express.js.
  • An account on Shoplazza and ngrok (for local development).
Steps:
  1. Set Up the Basic Express App

Install the required npm packages:

npm install express crypto axios
  1. Initialize Express and Required Libraries
const express = require("express");
const crypto = require("crypto");
const axios = require("axios");

const app = express();
  1. Define Constants

Replace CLIENT_ID and CLIENT_SECRET with the values you get from your Shoplazza Developer Platform Dashboard, you will need to create a public app to get these two credentials. BASE_URL should point to your server URL. In this example, it's using ngrok forward the local port 3000 to public for local development (See https://ngrok.com/ for more information), after you set up your ngrok tunnel, please replace https://015d-207-81-205-140.ngrok-free.app to the link ngrok generates for you:

const CLIENT_ID = "<YOUR_CLIENT_ID>";
const CLIENT_SECRET = "<YOUR_CLIENT_SECRET>";
const BASE_URL = "https://015d-207-81-205-140.ngrok-free.app";
const REDIRECT_URI = `${BASE_URL}/auth/shoplazza/callback`;
let access_token = {};
  1. HMAC Validation

The secureCompare function compares two strings securely to prevent timing attacks:

function secureCompare(a, b) {
    return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
}

The middleware hmacValidatorMiddleWare verifies the HMAC received in the request, you will need to construct a message for HMAC validation (See: https://www.shoplazza.dev/reference/oauth#hmac-validation for details):

function hmacValidatorMiddleWare(req, res, next) {
    const { code, hmac, state, shop } = req.query;
    const map = Object.assign({}, req.query);
    delete map["hmac"];
    const sortedKeys = Object.keys(map).sort();
    const message = sortedKeys.map(key => `${key}=${map[key]}`).join('&');

    const generated_hash = crypto
      .createHmac("sha256", CLIENT_SECRET)
      .update(message)
      .digest("hex");
    if (!secureCompare(generated_hash, hmac)) {
      return res.status(400).send("HMAC validation failed");
    }
    next();
}
  1. OAuth Flow

When the /auth/shoplazza route is accessed, the app will redirect the user to Shoplazza's OAuth page:

app.get("/auth/shoplazza", (req, res) => {
  const scopes = "read_customer";
  const state = crypto.randomBytes(16).toString("hex");

  res.redirect(
    `https://${req.query.shop}/admin/oauth/authorize?client_id=${CLIENT_ID}&scope=${scopes}&redirect_uri=${REDIRECT_URI}&response_type=code&state=${state}`
  );
});

After authorization, Shoplazza will redirect to the /auth/shoplazza/callback route:

app.get("/auth/shoplazza/callback", hmacValidatorMiddleWare, async (req, res) => {
    const { code, hmac, state, shop } = req.query;
  
    if (shop && hmac && code) {
        const { data } = await axios.post(`https://${shop}/admin/oauth/token`, {
            client_id: CLIENT_ID,
            client_secret: CLIENT_SECRET,
            code,
            grant_type: "authorization_code",
            redirect_uri: REDIRECT_URI,
        });
        access_token[shop] = data.access_token;
    
        const result = await axios({
            method: "get",
            url: `https://${shop}/openapi/2022-01/customers`,
            headers: {
               "Access-Token": access_token,
            },
        });
        res.status(200).send(result.data ? result.data : "No customer found");
    } else {
        res.status(400).send("Required parameters missing");
    }
});

Side notes: In this example, this is how to you fill in the URLs in the developer center. And of course, the ngrok link you generated won't be the same as the following example, please do remember to update the ngrok link to yours.

  1. Start the Server

Finally, start the Express server on port 3000

app.listen(3000, () => console.log("Server is listening on port 3000"));

Webhook Hmac validation

The HMAC verification process for OAuth is different from the process to verify webhooks.
Webhooks can be verified by calculating a digital signature. Each webhook request include a base64 encoded X-Shoplazza-Hmac-Sha256 header, which is generated using the app's Client Secret along with the data sent in the request.
To verify that the request came from Shoplazza, compute the HMAC digest according to the following algorithm and compare it to the value in the X-Shoplazza-Hmac-Sha256 header. If they match, then you can be sure that the webhook was sent from Shoplazza. As a best practice, the HMAC digest should be verified before the app responds to the webhook.
The following example use Ruby and Sinatra to verify a webhook request:

require 'rubygems'
require 'base64'
require 'openssl'
require 'sinatra'

# Shoplazza's Client Secret
SECRET = 'my_secret'

helpers do
  # Compare the computed HMAC digest based on the shared secret and the request contents to the reported HMAC in the headers
  def verify_webhook(data, hmac_header)
    calculated_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', SECRET, data))
    ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
  end
end

# Responds to the HTTP POST request
post '/' do
  request.body.rewind
  data = request.body.read
  verified = verify_webhook(data, env["X-Shoplazza-Hmac-Sha256"])

  puts "Webhook verified: #{verified}"
end

Step4: Pass the Scopes to obtain permission to access the APIs you need.

The application service receives this request for authorization. If there are no issues with the authorization, it will redirect using the 302 method.
For example:
https://rwerwre.myshoplaza.com/admin/oauth/authorize?client_id=ZwwR8eXIOq0Rr2XN3zUywxc0q-S9C3w4VkH3HnNUL_Q&redirect_uri=https%3A%2F%2Fstamped-review.apps.shoplazza.com%2Fapi%2Fauth%2Fcallback&response_type=code&scope=read_product+read_order+read_collection+read_shop+read_script_tags+write_script_tags+read_customer+read_price_rules+read_comments
Format:
https://${store_domian_name}/admin/oauth/authorize?client_id={client_id}&scope={scopes}&redirect_uri={redirect_uri}&response_type={response_type}&state={state}

  • store_domian_name:${store_name}.myshoplaza.com
  • store_name: The name of merchant's store.
  • client_id: The Client ID for the app. You can get it at https://partners.shoplazza.com/ after register app.
  • scope: A space separated list of [scopes]
  • redirect_uri: The URL to which a merchant is redirected after authorizing the app.
  • response_type: The response type of OAuth 2.0 process, here we need to fill in code
  • state: a random value, use to prevent CSRF attacks.

Scopes

Scope represents the permissions that a public application needs to request, with different permissions allowing access to different APIs. The value of the scope is chosen and assembled by the developer into the 302 redirect URL mentioned . On the merchant's app installation confirmation page, the merchant will be asked to authorize, as shown in the figure below:

Scopes Values

read_customer write_customer
read_order write_order
read_product write_product
read_collection write_collection
read_script_tags write_script_tags
read_app_proxy write_app_proxy
read_data write_data
read_shop
read_comments write_comments
read_price_rules write_price_rules
read_shop_navigation write_shop_navigation
read_search_api write_search_api
read_payment_info write_payment_info
read_themes
unauthenticated_read_checkouts unauthenticated_write_checkouts
unauthenticated_read_customers unauthenticated_write_customers
unauthenticated_read_customer_tags unauthenticated_read_content
unauthenticated_read_product_listings
unauthenticated_read_product_tags
unauthenticated_read_selling_plans

The Mapping Between API and Scope value

The mapping relationship with the API access path is as follows:

API Access URLscope
/productsproduct
/variantsproduct
/collectionscollection
/collectscollection
/payments_appspayment_info
/ordersorder
/shopshop
/price_rulesprice_rules
/discount_codesprice_rules
/couponsprice_rules
/discount_flashsalesprice_rules
/discount_rebatesprice_rules
/popupsprice_rules
/salespopsprice_rules
/redirectsshop_navigation
/themesthemes
/commentscomments
/script_tagsscript_tags
/customerscustomer
/gift_cardsgift_cards
/productsproduct
/pagesshop_navigation
/app-proxysapp_proxy
/datasdata
/app-proxiesapp_proxy
/data/data

Step 7: Open the App by Redirect Url

After merchant click "install app"

The shoplazza service will open the APP by a redirect url.
For example:
https://stamped-review.apps.shoplazza.com/api/auth/callback?code=wBe-NWHzW21e94YqD4bRKBsJsE2GcZlDzP4oW9w2ddk&hmac=4c396fac1912057b65228f5bbd4a65255961d85a60fba1f1105ddbf27f17b58f&shop=rwerwre.myshoplaza.com
Format:
{redirect_url}?code=${authorization_code}&hmac=${hamc}&shop=${store_domian_name}

Step 9: Get Access_token by Code

You can get an Access token by the following Request:
POST https://{store_name}.myshoplaza.com/admin/oauth/token
In this request, store_name is the name of the merchant's store and alongs with the following parameters:

  • client_id: The Client ID for the App.
  • client_secret: The Client secret key for the app.
  • code: The authorization_code provided in the redirect.
  • grant_type: The grant type of OAuth 2.0 process, please fill in authorization_code here.
  • redirect_uri: The redirect_uri of the app.

The server responds with an access token:

{
  "token_type": "Bearer",
  "expires_at": 1550546245,
  "access_token": "eyJ0eXAiOiJKV1QiLCJh",
  "refresh_token": "def502003d28ba08a964e",
  "store_id": "2",
  "store_name": "xiong1889"
}
  • token_type: It will just return Bearer.
  • expires_at: The access_token expired time, in timestamp.
  • access_token: The correct access_token.
  • refresh_token: The refresh token used to refresh the access_token if needed.
  • store_id: Store's ID in Shoplazza
  • store_name: Store name

OAuth SDK Available!
Similarly, you can quickly get an access token by SHOPLAZZA OAuth SDKs, for instance: Oauth-SDK-Go, see example below:

import (
  co "github.com/shoplazza-os/oauth-sdk-go"
  "github.com/shoplazza-os/oauth-sdk-go/shoplazza"
)

oauth := &co.Config{
                ClientID:     "s1Ip1WxpoEAHtPPzGiP2rK2Az-P07Nie7V97hRKigl4",
                ClientSecret: "0LFJcNqVb2Z1nVt9xT72vOo0sTWd6j8wVX60Y5xdzZZ",
                Endpoint:     shoplazza.Endpoint,
                RedirectURI:  "https://3830-43-230-206-233.ngrok.io/oauth_sdk/redirect_uri/",
                Scopes:       []string{"read_shop"},
        }
token, err := oauth.Exchange(context.Background(),"xxx.myshoplaza.com", "code"))

If the token expires, you can refresh the access_token with the following request:
POST https://{store_name}.myshoplaza.com/admin/oauth/token
In this request, following parameters are needed:

  • client_id: The Client ID that Shoplazza provided.
  • client_secret: The Client secret key that Shoplazza provided.
  • refresh_token: The refresh_token that mentioned above.
  • grant_type: The grant type of OAuth 2.0 process, please fill in refresh_token here.
  • redirect_uri: The redirect_uri of the app.

The server responds with an access token:

{
  "token_type": "Bearer",
  "expires_at": 1550546245,
  "access_token": "eyJ0eXAiOiJKV1QiLCJh",
  "refresh_token": "def502003d28ba08a964e",
  "store_id": "2",
  "store_name": "xiong1889"
}

OAuth SDK Available!
Similarly, you can quickly refresh the access token by SHOPLAZZA OAuth SDKs, for instance: Oauth-SDK-Go, see example below:

import (
  co "github.com/shoplazza-os/oauth-sdk-go"
  "github.com/shoplazza-os/oauth-sdk-go/shoplazza"
)

oauth := &co.Config{
                ClientID:     "s1Ip1WxpoEAHtPPzGiP2rK2Az-P07Nie7V97hRKigl4",
                ClientSecret: "0LFJcNqVb2Z1nVt9xT72vOo0sTWd6j8wVX60Y5xdzZZ",
                Endpoint:     shoplazza.Endpoint,
                RedirectURI:  "https://3830-43-230-206-233.ngrok.io/oauth_sdk/redirect_uri/",
                Scopes:       []string{"read_shop"},
        }
token, err := oauth.RefreshToken(context.Background(), "xxx.myshoplaza.com", "refresh token")

Develop the all functions by shoplazza API

After your app has retrieved an API access_token, it can make authenticated request to Admin API.
These request are accompanied with a header Access-Token: {access_token} where {access_token} is replaced with the permanent token.
The following request show how to retrieve a list of products using the Admin API

curl -i -X GET \
     -H "Content-Type:application/json" \
     -H "Access-Token:B_x-_5aVeXNwI-4AB98s5xLIvgv0fNzGf_MuTpqtIBA" \
     'https://store.myshoplaza.com/openapi/2020-01/products'