Unified.to
All articles

How to Integrate with Shopify


February 26, 2026

A Shopify API integration lets your product sync products, orders, customers, inventory, collections, and store data with Shopify merchants. For SaaS companies selling into ecommerce, that often means importing orders, updating inventory, creating draft orders, or pushing product data into downstream systems.

But a production-grade Shopify integration involves more than getting an API token and calling /products. You need to choose the right app distribution model, implement OAuth correctly, use the current Admin API strategy, design around rate limits, and keep data in sync with webhooks instead of polling.

This guide covers:

  1. How to integrate directly with the Shopify API
  2. The operational complexity you will own
  3. How to integrate Shopify using Unified's Commerce and Accounting APIs
  4. When to build direct vs use an integration layer

Direct Shopify API integration

Step 1: Use the GraphQL Admin API, not the REST Admin API

For new Shopify integrations, use the GraphQL Admin API. Shopify states that the REST Admin API became legacy on October 1, 2024, and instructs developers to build all new apps and integrations using GraphQL. Shopify also notes that new features ship in GraphQL first, while REST is in maintenance mode.

About REST to GraphQL migration

This matters for architecture right away:

  • GraphQL is the primary API for new Shopify work
  • REST can still matter for legacy integrations or passthrough edge cases
  • Bulk operations, pagination, and rate limits should be designed with GraphQL in mind

Step 2: Choose the right app distribution method

Shopify requires you to choose an app distribution type when you create the app, and that choice cannot be changed later.

The important distinction for SaaS teams is:

  • Public apps are for multi-merchant SaaS products and can be installed by many merchants
  • Custom apps are for a single store or a limited Shopify Plus organization
  • Admin-created custom apps are created directly inside one merchant's admin and are not the right fit for a multi-merchant SaaS product

If your product will be installed by many Shopify merchants, you want a public app. Shopify's app distribution docs explain these tradeoffs clearly.

About app distribution

Step 3: Create the app and configure allowed redirect URIs

You can create the app in the Shopify Dev Dashboard or with the Shopify CLI. Shopify documents both approaches, and the app configuration includes:

  • App URL
  • Allowed redirection URLs
  • Client ID
  • Client secret
  • Requested scopes

The redirect URIs used during OAuth must exactly match one of the allowed redirection URLs configured in the app. Create apps using the Dev Dashboard

Step 4: Implement OAuth for multi-merchant installs

For a B2B SaaS integration, Shopify requires OAuth. The client credentials flow is only for stores you own, not for public multi-merchant apps. Shopify's authentication docs make that distinction explicit.

Authentication overview

The authorization URL pattern looks like this:

GET https://{shop}/admin/oauth/authorize?client_id={client_id}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}

Key parameters:

  • shop: the merchant's .myshopify.com domain
  • client_id: your app's API key
  • scope: comma-separated scopes
  • redirect_uri: must exactly match an allowed redirect URI
  • state: random nonce for CSRF protection
  • grant_options[]: use per-user for online tokens; omit it for offline tokens

After approval, Shopify redirects back with parameters including code, hmac, shop, and state. Implement authorization code grant manually

Step 5: Exchange the code for an access token

Exchange the authorization code at:

POST https://{shop}.myshopify.com/admin/oauth/access_token

The body includes:

  • client_id
  • client_secret
  • code

Shopify responds with an access token and the granted scopes. Depending on token mode, the response may also include:

  • expires_in
  • associated_user
  • associated_user_scope
  • refresh_token
  • refresh_token_expires_in

Store both the merchant's shop domain and the token, because the shop domain becomes part of future API requests. Authorization code grant

Step 6: Choose offline vs online tokens carefully

Shopify supports both offline and online access tokens.

In practice:

  • Offline tokens are for background jobs, syncs, webhook processing, and long-lived app behavior
  • Online tokens are tied to a user session and user permissions

Shopify documents that online tokens expire after logout or after 24 hours, while offline tokens are meant for long-lived service access. For a SaaS integration that runs background sync or webhook processing, offline tokens are usually the right default. Offline and online access tokens

Core Shopify operations

Products

To list products, use the products connection in GraphQL. Shopify's GraphQL Admin API supports cursor-based pagination and lets you request exactly the fields you need.

A minimal product query looks like:

query GetProducts($cursor: String) {
  products(first: 10, after: $cursor) {
    edges {
      cursor
      node {
        id
        title
        handle
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

To update a product, use productUpdate. Shopify documents this mutation and notes that product updates require the write_products scope. productUpdate mutation

Orders

Use the orders connection to list orders. Shopify supports query filters and pagination through GraphQL.

A minimal query:

query GetOrders($cursor: String) {
  orders(first: 25, after: $cursor) {
    edges {
      cursor
      node {
        id
        name
        createdAt
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

One important operational note: by default, Shopify order access is typically limited to the most recent 60 days. If you need older orders, you must request read_all_orders in addition to the normal order scopes, and Shopify requires approval for that scope. API access scopes

Customers

Use the customers connection to retrieve customer data. Shopify supports filtering and search in GraphQL, including email and marketing-related criteria.

A typical customer query:

query GetCustomers($cursor: String) {
  customers(first: 50, after: $cursor, query: "accepts_marketing:true") {
    edges {
      cursor
      node {
        id
        displayName
        email
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

Inventory

Shopify separates inventory concepts into InventoryItem and InventoryLevel. For stock updates, the key mutation is inventoryAdjustQuantities, which applies delta-based adjustments rather than setting raw quantities in the simplest form. inventoryAdjustQuantities mutation

You will typically need:

  • read_inventory to read stock state
  • write_inventory to adjust stock

Fulfillment and returns

Shopify fulfillment workflows are based on fulfillment orders. Shopify automatically creates fulfillment orders when an order is placed; your app does not create them manually. To fulfill work, you use fulfillment-related queries and mutations such as fulfillmentCreate, depending on your app's scopes and permissions. fulfillmentCreate mutation

Shopify also has return objects and return-related scopes, including read_returns and write_returns, for apps that manage returns workflows. Returns scope reference

Shopify webhooks, pagination, and rate limits

Webhooks

Shopify webhooks are the right way to stay in sync with store changes. Shopify explicitly positions webhooks as the alternative to continuous polling.

Key webhook characteristics:

  • Shopify sends HTTP POST requests with a JSON payload
  • Webhooks include headers like:
    • X-Shopify-Topic
    • X-Shopify-Shop-Domain
    • X-Shopify-Hmac-Sha256
    • X-Shopify-Webhook-Id
  • Your app should validate the HMAC using your app secret
  • Shopify expects fast responses and recommends queueing work if processing takes longer than a few seconds

Shopify does not guarantee event ordering, and duplicate deliveries can happen, so idempotency is required. Webhooks overview

Pagination

For GraphQL Admin API, Shopify uses cursor-based pagination.

You paginate forward with:

  • first
  • after

And backward with:

  • last
  • before

Each response includes pageInfo with hasNextPage, hasPreviousPage, startCursor, and endCursor. Shopify documents a maximum of 250 nodes per page and recommends bulk operations for very large data retrievals. GraphQL pagination

Rate limits

For the GraphQL Admin API, Shopify uses cost-based throttling, not plain request counts.

Important points from Shopify's docs:

  • A single query's requested cost must stay within the allowed limits
  • Rate limits are scoped per app and store
  • The response includes cost metadata and throttle status
  • Bulk operations are the preferred path for large exports

Shopify also documents plan-based restore rates, so available throughput depends on the merchant's plan tier. API limits

If you still work with legacy REST endpoints, Shopify uses a leaky bucket algorithm there, and 429 Too Many Requests plus Retry-After handling still matter. But for new development, design around GraphQL cost and bulk operations.

Where Shopify integrations become operationally complex

A direct Shopify integration means you own:

  • App distribution strategy
  • OAuth installation flow per merchant
  • Online vs offline token decisions
  • GraphQL query design and cost management
  • Scope management for products, orders, customers, inventory, returns, and fulfillment
  • Historical order access approval for read_all_orders
  • Webhook verification and idempotency
  • Cursor pagination and large export workflows
  • Bulk operation orchestration

For a single internal app or one-off store integration, this can be manageable.

For a multi-merchant SaaS product, this becomes real integration infrastructure.

Integrating Shopify via Unified

From the Unified Shopify integration details you shared, Shopify spans multiple Unified categories, including:

  • Commerce
  • Accounting
  • Metadata
  • Passthrough

Step 1: Register OAuth for Unified

When integrating Shopify through Unified, set the Shopify callback URL to:

https://api.unified.to/oauth/code

Unified also provides regional variants:

  • https://api-eu.unified.to/oauth/code
  • https://api-au.unified.to/oauth/code

The merchant authorizes through Unified's embedded authorization flow, and Unified returns a connection_id that you store for later API calls.

Step 2: Use Unified's normalized objects

Based on the Shopify coverage you shared, Unified supports Shopify through normalized objects such as:

  • Commerce Item for products
  • Commerce ItemVariant for product variants
  • Commerce Inventory for inventory levels
  • Commerce Collection for collections/categories
  • Commerce Location
  • Accounting Contact for customers
  • Accounting Invoice and Accounting Salesorder for orders
  • Accounting CreditMemo
  • Metadata Metadata

That means a single integration surface can cover common product, order, customer, and inventory use cases without building directly against Shopify GraphQL.

Step 3: Make first API calls through Unified

List products:

curl -X GET "https://api.unified.to/commerce/{connection_id}/item?limit=100&offset=0" \
  -H "Authorization: Bearer YOUR_UNIFIED_API_KEY"

List orders:

curl -X GET "https://api.unified.to/accounting/{connection_id}/salesorder?limit=100" \
  -H "Authorization: Bearer YOUR_UNIFIED_API_KEY"

List customers:

curl -X GET "https://api.unified.to/accounting/{connection_id}/contact?limit=100" \
  -H "Authorization: Bearer YOUR_UNIFIED_API_KEY"

Update inventory:

curl -X PUT "https://api.unified.to/commerce/{connection_id}/inventory/{inventory_id}" \
  -H "Authorization: Bearer YOUR_UNIFIED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "available": 5,
    "item_variant_id": "VARIANT_ID",
    "location_id": "LOCATION_ID"
  }'

Create a sales order:

curl -X POST "https://api.unified.to/accounting/{connection_id}/salesorder" \
  -H "Authorization: Bearer YOUR_UNIFIED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "CONTACT_ID",
    "currency": "USD",
    "lineitems": [
      { "item_id": "ITEM_ID", "quantity": 2, "unit_price": "10.00" }
    ],
    "status": "draft"
  }'

Step 4: Use Unified webhooks

From the Shopify integration details you shared:

  • Some Shopify-backed Unified objects support virtual webhooks only
  • Some support native webhooks with virtual fallback
  • Products, collections, inventory, locations, and customers all have webhook coverage in some form
  • Orders are handled through Unified's webhook system rather than requiring you to build Shopify topic subscriptions directly

This is useful if you want normalized change notifications without implementing your own Shopify webhook ingestion and HMAC verification pipeline.

Step 5: Use passthrough for unsupported Shopify resources

Your research also shows that not every Shopify resource is normalized in Unified yet. In particular, fulfillments and returns are not broadly normalized in the current Shopify mapping you shared.

That is where passthrough matters.

Unified supports passthrough methods like:

  • GET
  • POST
  • PUT
  • DELETE

So if you need Shopify-specific endpoints not covered by Unified objects, you can still call them through Unified's passthrough layer while keeping the same connection model.

Direct vs Unified: when to choose each

Build directly with Shopify if:

  • Shopify is a core surface of your product
  • You need deep Shopify-specific GraphQL capabilities
  • You need full control over scopes, bulk operations, webhooks, and fulfillment workflows
  • You are comfortable managing GraphQL query cost and Shopify-specific operational details

Use Unified if:

  • You want one normalized API across Shopify and other commerce platforms
  • Your common use cases are products, orders, customers, inventory, and collections
  • You want a simpler auth and connection model
  • You want webhook handling abstracted
  • You still want a passthrough escape hatch for unsupported resources

Final thoughts

A Shopify API integration is straightforward at the 'hello world' level.

The complexity shows up in:

  • choosing the right app type
  • implementing merchant install flows
  • using GraphQL well
  • designing for webhook-first sync
  • managing scopes and bulk operations
  • handling high-volume stores safely

If Shopify is a strategic integration for your product, build with GraphQL Admin API first, use webhooks instead of polling, and design for multi-merchant behavior from the beginning.

If Shopify is one of several ecommerce integrations on your roadmap, a normalized integration layer can reduce engineering overhead and accelerate delivery.

→ Start your 30-day free trial

→ Book a demo

All articles