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:
- How to integrate directly with the Shopify API
- The operational complexity you will own
- How to integrate Shopify using Unified's Commerce and Accounting APIs
- 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.
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.
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.comdomainclient_id: your app's API keyscope: comma-separated scopesredirect_uri: must exactly match an allowed redirect URIstate: random nonce for CSRF protectiongrant_options[]: useper-userfor 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_idclient_secretcode
Shopify responds with an access token and the granted scopes. Depending on token mode, the response may also include:
expires_inassociated_userassociated_user_scoperefresh_tokenrefresh_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_inventoryto read stock statewrite_inventoryto 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-TopicX-Shopify-Shop-DomainX-Shopify-Hmac-Sha256X-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:
firstafter
And backward with:
lastbefore
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/codehttps://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 Itemfor productsCommerce ItemVariantfor product variantsCommerce Inventoryfor inventory levelsCommerce Collectionfor collections/categoriesCommerce LocationAccounting Contactfor customersAccounting InvoiceandAccounting Salesorderfor ordersAccounting CreditMemoMetadata 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.