2️⃣ Develop app

Build the parts of your app that talk to EasyStore. By the end of this page, your app will have three handlers:

App URL

Verifies EasyStore's request and starts installation, or shows your UI

Redirection URL

Exchanges the authorization code for an access token

Webhook URL

Cleans up when a merchant uninstalls your app

App installation

EasyStore uses OAuth 2.0's Authorization Code Grant to let merchants grant the EasyStore App permission to read or write store data. If the merchant consents to the requested permissions, a permission-scoped access token is issued to the app. The token can access the permitted resources in the API.

The installation process uses the App URL and Redirection URL set up in the app details page in the Partner Dashboard. You might need to update them to your latest URLs before you start.

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                App Installation Flow                                │
└─────────────────────────────────────────────────────────────────────────────────────┘

 ┌────────────────┐    ┌────────────────┐    ┌────────────────┐    ┌────────────────┐
 │    Merchant    │    │   EasyStore    │    │    Your App    │    │    Your App    │
 │                │    │  Admin + API   │    │    App URL     │    │Redirection URL │
 └────────┬───────┘    └────────┬───────┘    └────────┬───────┘    └────────┬───────┘
          │                     │                     │                     │
          │ 1. Click Install    │                     │                     │
          │────────────────────►│                     │                     │
          │                     │ 2. GET App URL      │                     │
          │                     │    shop, host_url,  │                     │
          │                     │    timestamp, hmac  │                     │
          │                     │────────────────────►│                     │
          │                     │                     │ 3. Verify hmac,     │
          │                     │                     │    check shop,      │
          │                     │                     │    installed?       │
 ┈┈┈┈┈┈┈┈┈│┈┈┈┈┈┈┈┈┈┈┈ Already installed: show your app UI, stop ┈┈┈┈┈┈┈┈┈┈┈│┈┈┈┈┈┈┈┈┈
          │                     │ 4. Redirect to      │                     │
          │                     │    oauth/authorize  │                     │
          │                     │    client_id, scope,│                     │
          │                     │    redirect_uri     │                     │
          │                     │◄────────────────────│                     │
          │ 5. Consent page     │                     │                     │
          │◄────────────────────│                     │                     │
          │    Approve          │                     │                     │
          │────────────────────►│                     │                     │
          │                     │ 6. GET Redirection  │                     │
          │                     │    URL: code, shop, │                     │
          │                     │    host_url, hmac,  │                     │
          │                     │    timestamp        │                     │
          │                     │──────────────────────────────────────────►│
          │                     │                     │        Verify hmac  │
          │                     │ 7. POST oauth/      │                     │
          │                     │    access_token.json│                     │
          │                     │    code, client_id, │                     │
          │                     │    client_secret    │                     │
          │                     │◄──────────────────────────────────────────│
          │                     │ 8. access_token,    │                     │
          │                     │    scope            │                     │
          │                     │──────────────────────────────────────────►│
          │                     │                     │    9. Save shop +   │
          │                     │                     │       access_token  │
          │                     │ 10. POST            │                     │
          │                     │     webhooks.json   │                     │
          │                     │     app/uninstall   │                     │
          │                     │◄──────────────────────────────────────────│
          │                     │ 11. Call the API    │                     │
          │                     │     with EasyStore- │                     │
          │                     │     Access-Token    │                     │
          │                     │◄──────────────────────────────────────────│
          ▼                     ▼                     ▼                     ▼

🧪 Test it on your development store. You don't need a review to install your own app. Open your app's details page in the Partner Dashboard, click View app, and click Install while logged in to your development store. Your App URL needs to be reachable on public HTTPS. During development, a tunnelling tool such as ngrok or Cloudflare Tunnel can expose your local server.

App URL Part 1: Start the installation
  1. The merchant clicks to install your app.
  2. EasyStore redirects the merchant to the App URL with a GET request containing the query parameters below:
    Query param Description
    shop The online store hostname, which is also the unique identifier of the store. Example: example.easy.co
    host_url The store's admin panel URL.
    hmac The HMAC-SHA256 hash used to verify the authenticity of the request.
    timestamp The request's timestamp in Unix format.

    You should verify the request's authenticity using the HMAC value.

  3. If the request is verified, also check that shop is a valid hostname ending with .easy.co. Your app later sends its Client Secret to https://{shop}, so never trust an unchecked value. Then use shop to check whether the store has installed this app before. For instance, the app can look up a record in its database using shop as the unique identifier. If the store is already installed, show your app's UI instead of starting the installation again.
  4. If the store is new, redirect the request to the EasyStore App Installation Authorization page. The URL structure is:
    {host_url}/oauth/authorize?client_id={client_id}&scope={scope}&redirect_uri={redirect_uri}

    Example:

    https://admin.easystore.co/oauth/authorize?client_id=app9a11dfd3d9aca1ef&scope=read_products,write_products&redirect_uri=https%3A%2F%2Fapps.yourapps.com%2Fauth%2Fcallback
    Component Description
    host_url The store's admin panel URL.
    client_id The app's unique Client ID, found in the app details page in the Partner Dashboard.
    scope The API access scopes (permissions) the app requests, separated by commas. See API access scopes for the possible values.
    redirect_uri The URL the merchant is redirected to after authorizing the app installation. It must exactly match one of the Redirection URLs set in the app details page in the Partner Dashboard. URL-encode the value, as in the Authentication guide. This matters most when it contains its own query string.
Redirection URL Part 2: Get the access token
  1. EasyStore shows an app installation consent page based on the scopes requested in the previous step.

    App installation consent page
  2. After the merchant authorizes, EasyStore redirects the merchant to the redirection URL from step 4 with a GET request containing the query parameters below:
    Query param Description
    shop The online store hostname, which is also the unique identifier of the store. Example: example.easy.co
    host_url The store's admin panel URL.
    hmac The HMAC-SHA256 hash used to verify the authenticity of the request.
    timestamp The request's timestamp in Unix format.
    code The authorization code used to exchange for an access token. Valid for 5 minutes and can only be redeemed once.

    You should verify the request's authenticity using the HMAC value.

  3. If the request is verified, call the Request access token API with the authorization code. Send the body as JSON with the Content-Type: application/json header. Example:
    curl --request POST 'https://{shop}/api/3.0/oauth/access_token.json' \
    --header 'Content-Type: application/json' \
    --data '{
        "code": "{authorization_code}",
        "client_id": "{client_id}",
        "client_secret": "{client_secret}"
    }'
  4. If the code, Client ID and Client Secret are valid, the API responds with an access token that can be used to call the respective API resources. Example:
    {
        "access_token": "f85632530bf277ec9ac6f649fc327f17",
        "scope": "read_products,write_products"
    }

    Store the access token. It stays valid until the merchant uninstalls the app. Common errors:

    Error message Reason
    Redeem timeout The authorization code is older than 5 minutes. Ask the merchant to install the app again.
    Code was already redeemed Each authorization code can only be exchanged once. Use the access token you stored the first time.
    Client ID and Secret not granted The client_id or client_secret doesn't match the app that issued the code.
Your server Part 3: Finish setting up the store
  1. Insert a record for this new installation into your database, so you can identify the store in the future.
  2. Subscribe to the app/uninstall webhook, so your app knows when the merchant uninstalls it:
    curl --request POST 'https://{shop}/api/3.0/webhooks.json' \
    --header 'EasyStore-Access-Token: {access_token}' \
    --header 'Content-Type: application/json' \
    --data '{
        "webhook": {
            "topic": "app/uninstall",
            "url": "https://apps.yourapps.com/webhooks/app-uninstall"
        }
    }'

    No extra scope is needed to subscribe to webhooks. Each app has one webhook URL per topic, so calling this again for the same topic updates the URL.

  3. You can now make API calls to read or write the store's data using the access token.

Calling the API

Send API requests to the store's shop hostname, with the access token in the EasyStore-Access-Token header:

curl 'https://{shop}/api/3.0/orders.json' \
--header 'EasyStore-Access-Token: {access_token}'
  • Base URL: https://{shop}/api/3.0/ for most resources. Newer resources such as inventory levels use https://{shop}/api/4.0/; see API versions.
  • Authentication: the EasyStore-Access-Token header on every request
  • Permissions: the token can only access the scopes the merchant granted during installation
  • Limits: see API call limit and Response status codes

Not sure where to start? Popular APIs explains the most used APIs (products, orders, inventory, customers and online store customisation). The full list of endpoints is in the API 3.0 and API 4.0 references.

App uninstallation

Uninstall an app in the admin panel

Merchants can uninstall an EasyStore App in the admin panel. Your app should subscribe to the app/uninstall webhook using the Webhooks API during the app installation process.

After the app is uninstalled from the store, the access token issued to the app for that store is revoked. Subscribed webhooks, created script tags, created snippets and created admin links are removed as well.

┌──────────────────────────────────────────────────────────────────────────────┐
│                           App Uninstallation Flow                            │
└──────────────────────────────────────────────────────────────────────────────┘

 ┌────────────────┐    ┌────────────────┐    ┌────────────────┐
 │    Merchant    │    │   EasyStore    │    │    Your App    │
 │                │    │  Admin + API   │    │  Webhook URL   │
 └────────┬───────┘    └────────┬───────┘    └────────┬───────┘
          │                     │                     │
          │ 1. Click Uninstall  │                     │
          │────────────────────►│                     │
          │                     │ 2. POST             │
          │                     │    app/uninstall    │
          │                     │    {"success":true} │
          │                     │────────────────────►│
          │                     │                     │ 3. Verify the
          │                     │                     │    Easystore-Hmac-
          │                     │                     │    Sha256 header
          │                     │                     │ 4. Delete the store
          │                     │                     │    record by the
          │                     │                     │    Easystore-Shop-
          │                     │                     │    Domain header
          │                     │ 5. Remove the       │
          │                     │    app's webhooks,  │
          │                     │    script tags,     │
          │                     │    snippets and     │
          │                     │    admin links;     │
          │                     │    revoke the token │
          ▼                     ▼                     ▼
Webhook URL Uninstallation steps
  1. The merchant requests to uninstall the app.
  2. EasyStore sends an app/uninstall webhook request to the subscribed webhook URL. It is sent immediately, before the app's webhooks, script tags and snippets are removed. The body is:
    {"success":true}
  3. The app should verify the webhook request's authenticity.
  4. If the webhook request is verified, the app can delete the store's database record using the Easystore-Shop-Domain header, which is the unique identifier of the store.

⚠️ Note: the app/uninstall webhook is only sent while the app is enabled in the store. If the merchant disables the app before uninstalling it, no webhook is sent, and API calls with the revoked token fail. Treat a rejected access token as a sign that the app may have been uninstalled.

How merchants interact with your app

Merchants access the apps module in the EasyStore admin panel. When they click into your app, one of two things happens:

  • (Suggested) An embedded page of your App URL is shown inside the EasyStore admin panel. Learn more
  • A new tab opens and redirects the merchant to your App URL. Learn more

Whether the app appears as an embedded UI or opens in a new tab depends on the Embedded in EasyStore Control Panel setting in the app details page.

Embedded in EasyStore Control Panel setting

Embedded UI

This approach provides the best experience for merchants, because it is seamless and they can manage the service inside the EasyStore admin panel.

EasyStore embeds the App URL set up in the app details page into the EasyStore admin panel using an iframe.

Every time the merchant opens your app, the App URL is loaded with a freshly signed shop, host_url, timestamp and hmac, the same as in installation step 2. Verify the HMAC on each load to identify the store. Because your page runs inside an iframe on the EasyStore admin domain, make sure it allows being framed (for example, don't send X-Frame-Options: DENY). Any cookies it relies on need SameSite=None; Secure.

                       ┌──────── EasyStore Admin Panel ────────┐
 ┌────────────────┐    │                                       │
 │    Your App    │    │  ┌─────────────────────────────────┐  │    ┌──────────┐
 │                │ ──►│  │  iframe loads your App URL      │◄─┼─── │ Merchant │
 └────────────────┘    │  │  ?shop=...&host_url=...         │  │    └──────────┘
    App URL            │  │   &timestamp=...&hmac=...       │  │     interacts
                       │  └─────────────────────────────────┘  │
                       └───────────────────────────────────────┘

Non-embedded UI

If the Embedded in EasyStore Control Panel option is disabled, the merchant is shown a page like the one below. When the merchant clicks Continue, a new browser tab opens the App URL.

Non-embedded app Continue page

Request HMAC verification

Every request from EasyStore contains an hmac parameter. Use this HMAC-SHA256 hash to verify that the request is valid and comes from EasyStore.

To verify it:

  1. Retrieve the query parameters from the request. (The hmac values in this walkthrough are illustrative. To check your code, use the test value below.)
    {
      "hmac": "665104118c1631a6529f1673b6224c4c0927aefe04b84dbba23d16828dc2f105",
      "host_url": "https://admin.easystore.co",
      "shop": "example.easy.co",
      "timestamp": 1696689587
    }
  2. Remove the hmac parameter.
    {
      "host_url": "https://admin.easystore.co",
      "shop": "example.easy.co",
      "timestamp": 1696689587
    }
  3. Sort the parameters by key in ascending order, because the key order affects the generated HMAC.

    Before:

    {
      "shop": "example.easy.co",
      "host_url": "https://admin.easystore.co",
      "timestamp": 1696689587
    }

    After:

    {
      "host_url": "https://admin.easystore.co",
      "shop": "example.easy.co",
      "timestamp": 1696689587
    }
  4. Convert the sorted parameters back to a query string.
    host_url=https://admin.easystore.co&shop=example.easy.co&timestamp=1696689587
  5. Generate an HMAC-SHA256 value from the query string using your app's Client Secret as the key.
  6. Compare the generated value with the hmac query parameter. If both values are the same, the request is valid and was sent by EasyStore.

Test your implementation

With the Client secret my_client_secret, the query string host_url=https://admin.easystore.co&shop=example.easy.co&timestamp=1696689587 must produce this HMAC:

623daafa2774b000feb2329fe2cf0d6d458be229c274347453fa1bd089cd770d

💡 Tip: build the string from the decoded values, as shown above. Don't percent-encode the URLs inside it. Also reject requests whose timestamp is too old (for example, older than a few minutes) to prevent replays.

PHP example
$data = $request->query();

$hmac1 = $data['hmac'];
unset($data['hmac']);
ksort($data);

$data = urldecode(http_build_query($data));
$hmac2 = hash_hmac('sha256', $data, env('CLIENT_SECRET'));

$isValid = hash_equals($hmac1, $hmac2);
Ruby on Rails example
data = request.query_parameters

hmac1 = data.delete(:hmac)
data = data.sort.to_h
data = CGI.unescape(data.to_query)

digest = OpenSSL::Digest.new('sha256')
hmac2 = OpenSSL::HMAC.hexdigest(digest, ENV["CLIENT_SECRET"], data)

is_valid = ActiveSupport::SecurityUtils.secure_compare(hmac1, hmac2)

Webhook HMAC verification

A webhook is a POST request sent from EasyStore to the subscribed webhook URL when certain events are triggered. Every webhook request from EasyStore contains an Easystore-Hmac-Sha256 header. Use this HMAC-SHA256 hash to verify that the webhook request is valid and comes from EasyStore.

Example headers (the hmac value is illustrative):

{
  "Easystore-Topic": "app/uninstall",
  "Easystore-Shop-Domain": "example.easy.co",
  "Easystore-Hmac-Sha256": "ff3ebb56faa3471e630d5eb3fe6e2217880e7ee5dcc920da493bcc4164b9a0c1",
  "Content-Type": "application/json"
}

To verify it:

  1. Retrieve the raw request body as a string.
  2. Generate an HMAC-SHA256 value from the string using your app's Client Secret as the key.
  3. Compare the generated value with the Easystore-Hmac-Sha256 header. If both values are the same, the webhook request is valid and was sent by EasyStore.

Test your implementation

With the Client secret my_client_secret, the raw body {"success":true} must produce this HMAC:

e8ec6d75a6236d1ac825d16ffc16ff5570d1cd6900747041dc9c51fc4e6bfcef

💡 Tip: compute the HMAC over the raw body exactly as received. Parsing the JSON and re-encoding it can change the bytes and break the check.

PHP example
$hmac1 = $request->header('Easystore-Hmac-Sha256');

$data = file_get_contents('php://input');
$hmac2 = hash_hmac('sha256', $data, env('CLIENT_SECRET'));

$isValid = hash_equals($hmac1, $hmac2);

Functionality building

📖 See Popular APIs for what the products, orders, inventory, customers and online store APIs can do, with their key endpoints.

You can provide additional functionality to the store. The EasyStore API lets you build functionalities including, but not limited to:

For logistic integrations, see the specialised Logistic Apps guide.
For payment integrations, contact dev@easystore.co for the payment integration guide.

Before you go live

Test the full flow on a development store:

  • A fresh install reaches your Redirection URL and stores the access token
  • Opening the app again (already installed) shows your UI without reinstalling
  • Requests with a wrong hmac, an old timestamp or a shop not ending in .easy.co are rejected
  • API calls work with the stored token and only use the scopes you requested
  • Uninstalling sends app/uninstall, and your app cleans up the store's data
  • Reinstalling after an uninstall works and gets a new access token
icon-accounticon-add-newicon-add-storeicon-appicon-appleicon-archiveicon-arrowdownicon-ascicon-bookicon-cancelicon-cart-addonicon-checkouticon-cherryicon-collectionicon-comfirmicon-confirmicon-couponicon-creditsicon-currencyicon-dashboardicon-discounticon-disintegrateicon-domainicon-dscicon-duplicateicon-editicon-emailicon-exclamation-triangleicon-exporticon-eyeicon-eye-slashicon-fullscreenicon-fullscreen-closeicon-generalicon-gifticon-gridicon-hddicon-helpicon-importicon-infoicon-integrationicon-invoiceicon-likeicon-listicon-locationicon-logouticon-new-tabicon-not-secureicon-optionicon-ordericon-outline-arrowdownicon-pageicon-paymenticon-plusicon-posicon-pricingicon-printericon-producticon-product-sumicon-product-sum-xicon-redirecticon-reporticon-reseticon-searchicon-secureicon-settingicon-shippingicon-staricon-storeicon-switch-storeicon-tagicon-taxesicon-templateicon-themeicon-tickicon-trashicon-unarchiveicon-uploadicon-user-tagicon-usersicon-weighticon-wholesale