Keeping inventory accurate and up‑to‑date is paramount for any high‑volume retailer. For Wellcare and Raf Pharmacy—two leading pharmacy chains in Qatar with over 10,000 SKUs, 100+ brick‑and‑mortar stores, and active sales across Shopify web stores, mobile apps, and POS systems—we built a robust, real‑time ERP‑to‑Shopify stock update solution. Below, we share our end‑to‑end architecture, code snippets, and performance results.
- Project Scope & Challenges
- 1. Initial Approach: Shopify REST API
- 2. Migrating to Shopify GraphQL API
- 3. Architecture Overview
- 4. Fetching Current Stock via GraphQL
- 5. Adjusting Inventory via GraphQL
- 6. Optimization Highlights
- 7. Performance Comparison
- 8. Technologies & Tools
- 9. Benefits to Wellcare & Raf Pharmacy
- 10. FAQs
Project Scope & Challenges
- Clients: Wellcare Pharmacy and Raf Pharmacy, Qatar
- Catalog Size: 10,000+ SKUs
- Sales Channels: Shopify storefront, headless mobile apps, 100+ physical locations
- ERP: INNSOF ERP pushing stock updates
- Requirements:
- Real‑time sync to prevent overselling
- Scalable for high update volume
- Minimal API latency and errors
- Accurate multi‑location stock handling
1. Initial Approach: Shopify REST API
We first used Shopify’s REST Admin API endpoint /admin/api/2024‑01/inventory_levels/set.json
to push stock updates.
// Sample REST payload
$payload = [
'location_id' => $locationId,
'inventory_item_id'=> $inventoryItemId,
'available' => $newStock
];
$response = $this->shopifyService->restRequest('POST', "/inventory_levels/set.json", $payload);
REST API Limitations
- No batch updates: One SKU per request
- Strict rate limits: ~2 calls/sec per store → bottleneck for thousands of SKUs
- High latency: 50–100 ms per request, accumulating delays
- Error-prone: Frequent rate‑limit errors under load
2. Migrating to Shopify GraphQL API
To overcome the REST limitations, we switched to Shopify’s GraphQL Admin API, unlocking:
- Batch updates with
inventoryAdjustQuantities
mutation - Cost‑based throttling (more flexible than REST)
- Lower network overhead by requesting only needed fields
- Delta‑based adjustments (only update when stock actually changes)
3. Architecture Overview
- ERP → Middleware
- INNSOF ERP sends a JSON payload via HTTP POST to our
StockController
.
- INNSOF ERP sends a JSON payload via HTTP POST to our
- Enrichment & Logging
- We parse, validate, and log the request in a local DB (
api_request_log
). - We enrich each SKU with
location_id
andinventory_item_id
(from cached tables).
- We parse, validate, and log the request in a local DB (
- Get Current Stock
- Fetch existing stock levels via GraphQL (per location & SKU).
- Compute Deltas
- Compare ERP stock vs. Shopify stock to determine
delta
.
- Compare ERP stock vs. Shopify stock to determine
- Adjust Inventory
- Send a single GraphQL mutation per batch (up to 100 SKUs).
- Response Handling
- Log successes/failures and push update status back to ERP.
- Retries & Fallback
- Automated cron jobs retry failed batches; fallback to REST if necessary.
4. Fetching Current Stock via GraphQL
Accurate delta calculation starts with retrieving existing quantities. We batch up to 60 items per query:
query inventoryItems {
item1: inventoryItem(id: "gid://shopify/InventoryItem/123456789") {
id
tracked
sku
inventoryLevels(first: 10) {
edges {
node {
location { id name }
quantities(names: "available") {
quantity
}
}
}
}
}
# item2, item3… up to 60
}
Our PHP method collects these into an array:
private function getCurrentStock(array $products): array {
$batches = array_chunk($products, 60);
$results = [];
foreach ($batches as $batch) {
// Build GraphQL query dynamically…
$response = $this->shopifyService->executeGraphQl($query, []);
// Parse `$response['data']` into $results[]
}
return $results; // [ ['inventory_item_id'=>…, 'location_id'=>…, 'quantity'=>…], … ]
}
5. Adjusting Inventory via GraphQL
Once we know the current stock, we prepare the delta update payload:
Mutation Template
mutation AdjustMultipleInventoryQuantities($input: InventoryAdjustQuantitiesInput!) {
inventoryAdjustQuantities(input: $input) {
inventoryAdjustmentGroup {
createdAt
changes { name delta }
}
userErrors { field message }
}
}
PHP Variables Format
$changes = [];
foreach ($updatedProducts as $p) {
$changes[] = [
'inventoryItemId'=> "gid://shopify/InventoryItem/{$p['inventory_item_id']}",
'locationId' => "gid://shopify/Location/{$p['location_id']}",
'delta' => $p['erp_stock'] - $p['current_stock']
];
}
$variables = [
'input' => [
'reason' => 'correction',
'name' => 'available',
'referenceDocumentUri' => 'logistics://erp/batch-2025-07-29',
'changes' => $changes
]
];
$response = $this->shopifyService->executeGraphQl($mutation, $variables);
This single call updates up to 100 SKUs at once, significantly reducing network chatter.
6. Optimization Highlights
- Batching & Chunking: 60 items/query for reads; 100 items/mutation for writes.
- Local Caching: Store
inventory_item_id
&location_id
in MySQL to avoid repetitive lookups. - Delta Logic: Only update SKUs whose current stock differs from ERP stock.
- Rate‑limit Awareness: Insert
sleep()
/usleep()
strategically and fallback to REST for urgent retries. - Robust Logging: Every request and response is logged in
api_request_log
andstock_update_log
tables.
7. Performance Comparison
Metric | REST API | GraphQL API |
---|---|---|
Batch update support | ❌ | ✅ |
Avg time per 100 SKUs | ~60 s | ~5 s |
Rate‑limit errors (under load) | Frequent | Rare |
Real-time accuracy | Moderate | High |
ERP feedback integration | Partial | Full, logged |
8. Technologies & Tools
- Shopify GraphQL Admin API
- PHP (StockController) with
ShopifyService
,LogManager
,DBOps
,ApiService
- MySQL for local caching and logs
- INNSOF ERP webhooks
- Cron Jobs for retries and ERP callbacks
9. Benefits to Wellcare & Raf Pharmacy
- 🔄 Real‑time stock accuracy across 100+ stores and channels
- 🚫 Zero oversells and seamless customer experience
- ⚡ High throughput: thousands of SKU updates per hour
- 🔍 Detailed audit trail for compliance and debugging
- 📈 Improved SEO & UX on web and mobile storefronts
10. FAQs
- Why fetch current stock before updating?
Ensures we calculate the correct delta, preventing accidental overwrites or duplicate adjustments. - How many SKUs can GraphQL handle in a batch?
Up to 100 SKUs per mutation and 60 per query, configurable based on store plan. - Can I mix REST and GraphQL?
Yes. We keep REST as a fallback for critical updates when GraphQL throttles. - Does multi‑location inventory work?
Absolutely. We query and update per-location levels usinglocationId
. - Is this approach adaptable to other ERPs?
Yes. Our modular design can integrate with Odoo, SAP, Oracle 6i, and more.
Why Choose Seamedia E‑commerce Solutions?
At Seamedia, we combine deep Shopify expertise with robust ERP integration know‑how to deliver tailored, high‑performance solutions that drive real business results. Our team—led by seasoned consultants like Prajosh VM—understands the complexities of multi‑location inventory, high SKU volumes, and omnichannel retail. We architect scalable, API‑driven workflows that keep your stock accurate, your customers happy, and your operations humming. Whether you’re a fast‑growing pharmacy chain, a national retailer, or an enterprise manufacturer, Seamedia offers end‑to‑end development, seamless ERP connectivity, and 24/7 support.
Ready to streamline your inventory and supercharge your sales?
Contact us today at hello@seamedia.in or visit www.seamedia.in to discuss how we can build a custom real‑time stock sync solution for your business.