Shopify & WooCommerce inventory sync with Google Sheets alerts
Pull contacts, verify each address with BillionVerify, and continue to Shopify β only deliverable addresses get through.
Why verify before the send
Sending to invalid, risky, catch-all, or disposable addresses spikes your bounce rate and erodes sender reputation. A verification gate before the Shopify step removes that risk automatically β only deliverable addresses continue, the rest are flagged.
The workflow
BillionVerify β verification sits right before the send.
Node by node
- 1β° Hourly Reconciliation TriggerTriggerΒ· n8n
Starts the workflow β on a schedule, a webhook, or manually while you test.
- 2π₯ Platform Webhook ReceiverSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 3π Parse Trigger DataSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 4π Prepare Update ActionsSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 5ποΈ Fetch Shopify InventorySourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 6π Fetch WooCommerce InventorySourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 7π¨ Needs Manual Review?LogicΒ· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 8π Merge All Platform DataSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 9π Log to Google Sheets AuditSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 10π Split Updates by PlatformSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 11π Compare & Identify DiscrepanciesSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 12π§ Send Notification?LogicΒ· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 13π Route Updates to PlatformsLogicΒ· n8n
Routes items based on the workflow logic.
- 14β Has Discrepancies?LogicΒ· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 15Verify Email (BillionVerify)VerifyΒ· billionverify
The BillionVerify node verifies the address β status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score β before anything is sent.
- 16π€ Return ResponseSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 17ποΈ Update ShopifySourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 18π Update WooCommerceSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 19π Split Discrepancies for AI ReviewSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 20IF deliverableLogicΒ· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 21π Merge Update ResultsSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 22βοΈ Send Alert EmailSendΒ· n8n
Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.
Workflow JSON
Copy or download this workflow, then import it in n8n (Workflows β Import from File / Paste). Install the BillionVerify community node first, then add your API key credential.
{
"name": "Shopify & WooCommerce inventory sync with Google Sheets alerts + BillionVerify",
"nodes": [
{
"id": "01889a3e-2114-48d1-b95c-d15f544888bb",
"name": "β° Hourly Reconciliation Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2080,
0
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "8ddc77f0-6103-4f44-a9d4-c24ea2b109bc",
"name": "π₯ Platform Webhook Receiver",
"type": "n8n-nodes-base.webhook",
"position": [
-2752,
-272
],
"webhookId": "inventory-sync-webhook",
"parameters": {
"path": "inventory-webhook",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "e85773c1-3541-41aa-9fd5-3b962ca4f224",
"name": "π Parse Trigger Data",
"type": "n8n-nodes-base.set",
"position": [
-1888,
192
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "triggerType",
"name": "triggerType",
"type": "string",
"value": "={{ $json.body ? 'webhook' : 'schedule' }}"
},
{
"id": "sku",
"name": "sku",
"type": "string",
"value": "={{ $json.body?.sku || 'ALL' }}"
},
{
"id": "platform",
"name": "platform",
"type": "string",
"value": "={{ $json.body?.platform || 'ALL' }}"
},
{
"id": "syncMode",
"name": "syncMode",
"type": "string",
"value": "={{ $json.body?.sku ? 'single' : 'full' }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "1400a211-e04c-4a41-916d-0d7857de5cb6",
"name": "ποΈ Fetch Shopify Inventory",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1664,
16
],
"parameters": {
"url": "https://{{ $env.SHOPIFY_STORE }}.myshopify.com/admin/api/2024-01/products.json",
"options": {
"timeout": 30000
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "limit",
"value": "250"
},
{
"name": "fields",
"value": "id,title,variants"
}
]
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "48d97f0b-ab09-40cb-b6d1-63f0413a3f0f",
"name": "π Fetch WooCommerce Inventory",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1648,
192
],
"parameters": {
"url": "={{ $env.WOOCOMMERCE_URL }}/wp-json/wc/v3/products",
"options": {
"timeout": 30000
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{
"name": "per_page",
"value": "100"
},
{
"name": "status",
"value": "publish"
}
]
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "a0c14f75-e5d6-4f93-9e00-099b90fd4ce1",
"name": "π Merge All Platform Data",
"type": "n8n-nodes-base.merge",
"position": [
-1408,
176
],
"parameters": {
"mode": "combine",
"options": {}
},
"typeVersion": 3
},
{
"id": "55b1e266-2955-4278-8c10-7aa6db851e12",
"name": "π Compare & Identify Discrepancies",
"type": "n8n-nodes-base.code",
"position": [
-1184,
176
],
"parameters": {
"jsCode": "// Inventory Data Normalization & Comparison Engine\n// Standardizes data from all platforms and identifies discrepancies\n\nconst inputData = $input.all();\n\n// Initialize storage\nconst inventoryByPlatform = {\n shopify: [],\n woocommerce: [],\n magento: [],\n erpnext: [],\n customdb: []\n};\n\n// Parse and normalize data from each platform\nfor (const item of inputData) {\n const json = item.json;\n \n // Shopify data\n if (json.products) {\n for (const product of json.products) {\n for (const variant of product.variants || []) {\n inventoryByPlatform.shopify.push({\n sku: variant.sku || variant.id.toString(),\n quantity: variant.inventory_quantity || 0,\n productName: product.title,\n variantId: variant.id,\n platform: 'shopify',\n lastUpdated: variant.updated_at\n });\n }\n }\n }\n \n // WooCommerce data\n if (json.id && json.sku && json.stock_quantity !== undefined) {\n inventoryByPlatform.woocommerce.push({\n sku: json.sku,\n quantity: parseInt(json.stock_quantity) || 0,\n productName: json.name,\n productId: json.id,\n platform: 'woocommerce',\n lastUpdated: json.date_modified\n });\n }\n \n // Magento data\n if (json.items) {\n for (const product of json.items) {\n if (product.extension_attributes?.stock_item) {\n inventoryByPlatform.magento.push({\n sku: product.sku,\n quantity: parseInt(product.extension_attributes.stock_item.qty) || 0,\n productName: product.name,\n productId: product.id,\n platform: 'magento',\n lastUpdated: product.updated_at\n });\n }\n }\n }\n \n // ERPNext data (Master source)\n if (json.data) {\n for (const item of json.data) {\n inventoryByPlatform.erpnext.push({\n sku: item.item_code,\n quantity: parseInt(item.stock_qty) || 0,\n productName: item.name,\n platform: 'erpnext',\n isMaster: true,\n lastUpdated: item.modified\n });\n }\n }\n \n // Custom Database data\n if (json.sku && json.quantity !== undefined) {\n inventoryByPlatform.customdb.push({\n sku: json.sku,\n quantity: parseInt(json.quantity) || 0,\n warehouseLocation: json.warehouse_location,\n platform: 'customdb',\n lastUpdated: json.last_updated\n });\n }\n}\n\n// Consolidate all SKUs\nconst allSKUs = new Set();\nfor (const platform in inventoryByPlatform) {\n for (const item of inventoryByPlatform[platform]) {\n if (item.sku) allSKUs.add(item.sku);\n }\n}\n\n// Compare inventory across platforms for each SKU\nconst discrepancies = [];\nconst consistentItems = [];\n\nfor (const sku of allSKUs) {\n const skuData = {\n sku: sku,\n platforms: {}\n };\n \n // Collect data from each platform\n for (const platform in inventoryByPlatform) {\n const platformItem = inventoryByPlatform[platform].find(item => item.sku === sku);\n if (platformItem) {\n skuData.platforms[platform] = {\n quantity: platformItem.quantity,\n lastUpdated: platformItem.lastUpdated,\n productName: platformItem.productName,\n isMaster: platformItem.isMaster || false,\n productId: platformItem.productId,\n variantId: platformItem.variantId\n };\n }\n }\n \n // Calculate statistics\n const quantities = Object.values(skuData.platforms).map(p => p.quantity);\n const avgQuantity = quantities.reduce((a, b) => a + b, 0) / quantities.length;\n const maxQuantity = Math.max(...quantities);\n const minQuantity = Math.min(...quantities);\n const variance = maxQuantity - minQuantity;\n const variancePercent = avgQuantity > 0 ? (variance / avgQuantity) * 100 : 0;\n \n skuData.statistics = {\n average: avgQuantity,\n max: maxQuantity,\n min: minQuantity,\n variance: variance,\n variancePercent: variancePercent.toFixed(2),\n platformCount: Object.keys(skuData.platforms).length\n };\n \n // Determine if there's a discrepancy\n if (variance > 0 && variancePercent > 5) {\n // Significant discrepancy found\n skuData.hasDiscrepancy = true;\n skuData.severity = variancePercent > 20 ? 'critical' : variancePercent > 10 ? 'warning' : 'info';\n skuData.recommendedAction = 'ai_review';\n \n // Use ERPNext as source of truth if available\n if (skuData.platforms.erpnext) {\n skuData.sourceOfTruth = 'erpnext';\n skuData.correctQuantity = skuData.platforms.erpnext.quantity;\n skuData.recommendedAction = 'sync_to_erpnext';\n } else {\n // Use most recently updated platform\n let mostRecent = null;\n let mostRecentTime = null;\n \n for (const [platform, data] of Object.entries(skuData.platforms)) {\n const updateTime = new Date(data.lastUpdated);\n if (!mostRecentTime || updateTime > mostRecentTime) {\n mostRecentTime = updateTime;\n mostRecent = platform;\n }\n }\n \n skuData.sourceOfTruth = mostRecent;\n skuData.correctQuantity = skuData.platforms[mostRecent].quantity;\n skuData.recommendedAction = `sync_to_${mostRecent}`;\n }\n \n discrepancies.push(skuData);\n } else {\n // Inventory is consistent\n skuData.hasDiscrepancy = false;\n skuData.severity = 'none';\n consistentItems.push(skuData);\n }\n}\n\n// Sort discrepancies by severity\ndiscrepancies.sort((a, b) => {\n const severityOrder = { critical: 0, warning: 1, info: 2 };\n return severityOrder[a.severity] - severityOrder[b.severity];\n});\n\n// Return comprehensive analysis\nreturn [{\n json: {\n summary: {\n totalSKUs: allSKUs.size,\n discrepanciesFound: discrepancies.length,\n consistentItems: consistentItems.length,\n criticalDiscrepancies: discrepancies.filter(d => d.severity === 'critical').length,\n warningDiscrepancies: discrepancies.filter(d => d.severity === 'warning').length,\n platformsConnected: Object.keys(inventoryByPlatform).filter(p => inventoryByPlatform[p].length > 0).length,\n timestamp: new Date().toISOString()\n },\n discrepancies: discrepancies,\n consistentItems: consistentItems.slice(0, 10), // Sample of consistent items\n platformData: inventoryByPlatform\n }\n}];"
},
"typeVersion": 2
},
{
"id": "148d6a37-1d40-453e-a682-f2afcb90931c",
"name": "β Has Discrepancies?",
"type": "n8n-nodes-base.if",
"position": [
-960,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "has-discrepancies",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.summary.discrepanciesFound }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2
},
{
"id": "ab9dd3c3-8a6a-4fbf-b715-31852d9d63a0",
"name": "π Split Discrepancies for AI Review",
"type": "n8n-nodes-base.code",
"position": [
-704,
80
],
"parameters": {
"jsCode": "// Split discrepancies into individual items for AI analysis\nconst data = $input.first().json;\nconst discrepancies = data.discrepancies || [];\n\n// Return each discrepancy as separate item for AI processing\nreturn discrepancies.map(item => ({\n json: {\n sku: item.sku,\n platforms: item.platforms,\n statistics: item.statistics,\n severity: item.severity,\n sourceOfTruth: item.sourceOfTruth,\n correctQuantity: item.correctQuantity,\n recommendedAction: item.recommendedAction,\n originalData: item\n }\n}));"
},
"typeVersion": 2
},
{
"id": "b9ca15c4-4ad0-4ca5-8aaa-c63805a2b3d0",
"name": "π Prepare Update Actions",
"type": "n8n-nodes-base.code",
"position": [
-2624,
-400
],
"parameters": {
"jsCode": "// Parse AI response and prepare update actions\nconst item = $input.first().json;\n\nlet aiDecision;\ntry {\n // Extract JSON from AI response\n const responseText = item.response || item.text || JSON.stringify(item);\n const jsonMatch = responseText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n aiDecision = JSON.parse(jsonMatch[0]);\n } else {\n throw new Error('No JSON found in AI response');\n }\n} catch (error) {\n console.error('Failed to parse AI response:', error);\n // Fallback to original recommendation\n aiDecision = {\n decision: 'approve',\n sourceOfTruth: item.sourceOfTruth,\n correctQuantity: item.correctQuantity,\n reasoning: 'Using original recommendation due to AI parse error',\n confidence: 'low',\n requiresManualReview: true\n };\n}\n\n// Prepare update instructions for each platform\nconst updates = [];\n\nif (aiDecision.decision === 'approve' && !aiDecision.requiresManualReview) {\n for (const [platform, data] of Object.entries(item.platforms)) {\n if (platform !== aiDecision.sourceOfTruth && data.quantity !== aiDecision.correctQuantity) {\n updates.push({\n platform: platform,\n sku: item.sku,\n currentQuantity: data.quantity,\n newQuantity: aiDecision.correctQuantity,\n productId: data.productId,\n variantId: data.variantId,\n action: 'update',\n reason: aiDecision.reasoning\n });\n }\n }\n}\n\nreturn [{\n json: {\n sku: item.sku,\n aiDecision: aiDecision,\n updates: updates,\n originalData: item,\n timestamp: new Date().toISOString(),\n requiresManualReview: aiDecision.requiresManualReview || aiDecision.decision !== 'approve'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "0870838d-5ca8-4692-bd7b-784bdb5a0b68",
"name": "π¨ Needs Manual Review?",
"type": "n8n-nodes-base.if",
"position": [
-2464,
-400
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "needs-manual-review",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.requiresManualReview }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "f6bf18e8-b3e4-48ab-9fdc-b4feeb8295e9",
"name": "π Split Updates by Platform",
"type": "n8n-nodes-base.code",
"position": [
-2304,
-224
],
"parameters": {
"jsCode": "// Split updates by platform for parallel execution\nconst item = $input.first().json;\nconst updates = item.updates || [];\n\nif (updates.length === 0) {\n return [{ json: { message: 'No updates needed', sku: item.sku } }];\n}\n\nreturn updates.map(update => ({\n json: {\n ...update,\n sku: item.sku,\n aiDecision: item.aiDecision\n }\n}));"
},
"typeVersion": 2
},
{
"id": "7345be2f-446c-44a8-a51c-edfc70aab70d",
"name": "π Route Updates to Platforms",
"type": "n8n-nodes-base.switch",
"position": [
-2080,
-224
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "shopify",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5b747c86-4713-42ff-9ec6-a5e64dd1ef8a",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.platform }}",
"rightValue": "shopify"
}
]
},
"renameOutput": true
},
{
"outputKey": "woocommerce",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2d5fbb50-7488-4e14-83a6-9abce83cfc78",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.platform }}",
"rightValue": "woocommerce"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3
},
{
"id": "2dbb54af-3007-40ec-88ce-c61c9efb7662",
"name": "ποΈ Update Shopify",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1856,
-320
],
"parameters": {
"url": "=https://{{ $env.SHOPIFY_STORE }}.myshopify.com/admin/api/2024-01/inventory_levels/set.json",
"method": "PUT",
"options": {},
"sendBody": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "location_id",
"value": "={{ $env.SHOPIFY_LOCATION_ID }}"
},
{
"name": "inventory_item_id",
"value": "={{ $json.variantId }}"
},
{
"name": "available",
"value": "={{ $json.newQuantity }}"
}
]
},
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "credential-id",
"name": "httpHeaderAuth Credential"
}
},
"typeVersion": 4.2
},
{
"id": "925d0b5f-3040-4118-b2ab-90d19c9747c0",
"name": "π Update WooCommerce",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1856,
-144
],
"parameters": {
"url": "={{ $env.WOOCOMMERCE_URL }}/wp-json/wc/v3/products/{{ $json.productId }}",
"method": "PUT",
"options": {},
"jsonBody": "={\n \"stock_quantity\": {{ $json.newQuantity }}\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"typeVersion": 4.2
},
{
"id": "af4349e9-c55d-4136-ac7c-c8bea746c26c",
"name": "π Merge Update Results",
"type": "n8n-nodes-base.merge",
"position": [
-1632,
-224
],
"parameters": {
"mode": "combine",
"options": {}
},
"typeVersion": 3
},
{
"id": "4147cc43-a94d-4645-a6c2-aa4b67e23b2d",
"name": "π Log to Google Sheets Audit",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1376,
-352
],
"parameters": {
"columns": {
"value": {
"sku": "={{ $json.sku }}",
"action": "={{ $json.action }}",
"reason": "={{ $json.reason }}",
"platform": "={{ $json.platform }}",
"timestamp": "={{ $now.toISO() }}",
"confidence": "={{ $json.aiDecision?.confidence }}",
"newQuantity": "={{ $json.newQuantity }}",
"oldQuantity": "={{ $json.currentQuantity }}",
"manualReview": "={{ $json.requiresManualReview }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Audit Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEETS_AUDIT_ID }}"
}
},
"typeVersion": 4.5
},
{
"id": "6861a3f1-bb62-4cc5-ab13-8193aa4465d6",
"name": "π§ Send Notification?",
"type": "n8n-nodes-base.if",
"position": [
-1216,
-352
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "is-critical",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.aiDecision?.requiresManualReview || $json.severity === 'critical' }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "38882834-1ccc-496a-b5e2-a84d79b91fba",
"name": "βοΈ Send Alert Email",
"type": "n8n-nodes-base.gmail",
"position": [
-1008,
-368
],
"webhookId": "80d4e67a-5013-44ee-b974-f010e230221a",
"parameters": {
"sendTo": "={{ $env.NOTIFICATION_EMAIL }}",
"message": "=<html>\n<body style=\"font-family: Arial, sans-serif;\">\n <h2>Inventory Synchronization Alert</h2>\n \n <div style=\"background: #fff3cd; padding: 15px; border-radius: 5px; margin: 20px 0;\">\n <h3>β οΈ Manual Review Required</h3>\n <p><strong>SKU:</strong> {{ $json.sku }}</p>\n <p><strong>Severity:</strong> {{ $json.severity }}</p>\n </div>\n \n <h3>Current Inventory Status:</h3>\n <table style=\"border-collapse: collapse; width: 100%;\">\n <tr style=\"background: #f8f9fa;\">\n <th style=\"border: 1px solid #ddd; padding: 8px;\">Platform</th>\n <th style=\"border: 1px solid #ddd; padding: 8px;\">Quantity</th>\n <th style=\"border: 1px solid #ddd; padding: 8px;\">Last Updated</th>\n </tr>\n {{ Object.entries($json.platforms).map(([platform, data]) => `\n <tr>\n <td style=\"border: 1px solid #ddd; padding: 8px;\">${platform}</td>\n <td style=\"border: 1px solid #ddd; padding: 8px;\">${data.quantity}</td>\n <td style=\"border: 1px solid #ddd; padding: 8px;\">${data.lastUpdated}</td>\n </tr>\n `).join('') }}\n </table>\n \n <h3>AI Analysis:</h3>\n <p><strong>Recommended Action:</strong> {{ $json.aiDecision?.reasoning }}</p>\n <p><strong>Confidence:</strong> {{ $json.aiDecision?.confidence }}</p>\n \n <p style=\"margin-top: 30px; color: #6c757d; font-size: 12px;\">\n Timestamp: {{ $now.toISO() }}<br>\n This is an automated message from the Inventory Sync System.\n </p>\n</body>\n</html>",
"options": {},
"subject": "=π¨ Inventory Sync Alert: {{ $json.sku }}"
},
"typeVersion": 2.1
},
{
"id": "3a7c80d3-b584-4f0d-9e56-133835759f2b",
"name": "π€ Return Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-720,
-192
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ {\n \"success\": true,\n \"summary\": $('π Compare & Identify Discrepancies').item.json.summary,\n \"discrepanciesProcessed\": $('π Prepare Update Actions').all().length,\n \"updatesApplied\": $('π Merge Update Results').all().length,\n \"timestamp\": $now.toISO()\n} }}"
},
"typeVersion": 1.1
},
{
"id": "99763021-7179-4d4a-b37b-35d5cf2d03a4",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2784,
-784
],
"parameters": {
"width": 448,
"height": 256,
"content": "## How It Works\nA webhook or timer triggers the workflow to automatically fetch inventory data from multiple platforms. Stock levels are compared across stores to identify discrepancies, and any inconsistencies are updated on the respective platforms in real time. All changes and updates are recorded in Google Sheets for easy tracking, and email alerts are sent to notify relevant team members of any exceptions or issues that require attention. This ensures inventory accuracy and timely response to stock mismatches.\n\n"
},
"typeVersion": 1
},
{
"id": "36d24781-1b5a-4d91-9001-0682255e8a35",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1824,
-784
],
"parameters": {
"color": 4,
"width": 400,
"height": 272,
"content": "## Setup Instructions\n1. Add Shopify/WooCommerce API credentials.\n2. Link Google Sheets and Gmail.\n3. Adjust sync frequency in Function nodes.\n\n## Prerequisites\n- Shopify/WooCommerce API keys\n- Google Sheets access\n- Gmail credentials\n- n8n instance\n\n"
},
"typeVersion": 1
},
{
"id": "3f2a21ab-a4b3-4728-9e2b-722c86e29d48",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1392,
-784
],
"parameters": {
"color": 3,
"width": 304,
"height": 256,
"content": "## Customization\n- Add ERPNext or custom APIs\n- Enable Slack notifications\n- AI discrepancy detection\n\n## Benefits\n- Real-time inventory accuracy\n- Automated cross-platform updates\n"
},
"typeVersion": 1
},
{
"id": "f647ebf9-8bf3-46ea-adb6-defc4c7ebca8",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2304,
-784
],
"parameters": {
"width": 448,
"height": 208,
"content": "## Setup Steps\n1. Add Shopify and/or WooCommerce API credentials to enable secure data access.\n2. Connect Google Sheets for comprehensive logging of all inventory updates and Gmail for sending timely email alerts.\n3. Configure the sync frequency within the Function nodes to control how often inventory data is fetched, compared, and updated."
},
"typeVersion": 1
},
{
"id": "d8b80508-910b-4a02-ba69-25f0e97f5047",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2784,
-512
],
"parameters": {
"color": 7,
"width": 608,
"height": 528,
"content": "## Data Collection\nFetches current inventory from Shopify and WooCommerce APIs, then normalizes product data into a consistent format for comparison."
},
"typeVersion": 1
},
{
"id": "0d490778-6491-4c03-801f-b5e754c14658",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1424,
-96
],
"parameters": {
"color": 7,
"width": 640,
"height": 448,
"content": "## Discrepancy Detection\nCompares stock levels by SKU across platforms. Flags mismatches and determines which platform has the authoritative count.\n"
},
"typeVersion": 1
},
{
"id": "f2b64093-ddd3-4618-ba92-3083d48b2dbc",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2160,
-512
],
"parameters": {
"color": 7,
"width": 720,
"height": 896,
"content": "## Platform Updates\nPushes corrected inventory values back to Shopify and WooCommerce to maintain sync across both stores."
},
"typeVersion": 1
},
{
"id": "2a8cb55f-7e37-450a-a7a7-ee071d95f0cb",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-768,
-512
],
"parameters": {
"color": 7,
"width": 288,
"height": 864,
"content": "## Logging & Alerts\nRecords all sync operations to Google Sheets with timestamps. Sends email notifications for discrepancies or sync failures.\n\n"
},
"typeVersion": 1
},
{
"id": "e9a33c02-317b-4922-970f-787d277b9a48",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1424,
-512
],
"parameters": {
"color": 7,
"width": 640,
"height": 384,
"content": "## Error Handling\nCatches API failures, connection issues, or data format problems. Ensures partial failures don't break the entire sync process."
},
"typeVersion": 1
},
{
"parameters": {
"operation": "verify",
"email": "={{ $env.NOTIFICATION_EMAIL }}",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
-1368,
-368
],
"name": "Verify Email (BillionVerify)",
"credentials": {
"billionVerifyApi": {
"id": "",
"name": "BillionVerify account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "is-deliverable",
"leftValue": "={{ $json.is_deliverable }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-1188,
-368
],
"name": "IF deliverable"
}
],
"connections": {
"β Has Discrepancies?": {
"main": [
[
{
"node": "π Split Discrepancies for AI Review",
"type": "main",
"index": 0
}
],
[
{
"node": "π€ Return Response",
"type": "main",
"index": 0
}
]
]
},
"ποΈ Update Shopify": {
"main": [
[
{
"node": "π Merge Update Results",
"type": "main",
"index": 0
}
]
]
},
"βοΈ Send Alert Email": {
"main": [
[
{
"node": "π€ Return Response",
"type": "main",
"index": 0
}
]
]
},
"π Parse Trigger Data": {
"main": [
[
{
"node": "ποΈ Fetch Shopify Inventory",
"type": "main",
"index": 0
},
{
"node": "π Fetch WooCommerce Inventory",
"type": "main",
"index": 0
}
]
]
},
"π§ Send Notification?": {
"main": [
[
{
"node": "Verify Email (BillionVerify)",
"type": "main",
"index": 0
}
],
[
{
"node": "π€ Return Response",
"type": "main",
"index": 0
}
]
]
},
"π Update WooCommerce": {
"main": [
[
{
"node": "π Merge Update Results",
"type": "main",
"index": 1
}
]
]
},
"π Merge Update Results": {
"main": [
[
{
"node": "π Log to Google Sheets Audit",
"type": "main",
"index": 0
}
]
]
},
"π¨ Needs Manual Review?": {
"main": [
[
{
"node": "π Log to Google Sheets Audit",
"type": "main",
"index": 0
}
],
[
{
"node": "π Split Updates by Platform",
"type": "main",
"index": 0
}
]
]
},
"π Prepare Update Actions": {
"main": [
[
{
"node": "π¨ Needs Manual Review?",
"type": "main",
"index": 0
}
]
]
},
"π Merge All Platform Data": {
"main": [
[
{
"node": "π Compare & Identify Discrepancies",
"type": "main",
"index": 0
}
]
]
},
"π₯ Platform Webhook Receiver": {
"main": [
[
{
"node": "π Parse Trigger Data",
"type": "main",
"index": 0
},
{
"node": "π Prepare Update Actions",
"type": "main",
"index": 0
}
]
]
},
"π Split Updates by Platform": {
"main": [
[
{
"node": "π Route Updates to Platforms",
"type": "main",
"index": 0
}
]
]
},
"π Log to Google Sheets Audit": {
"main": [
[
{
"node": "π§ Send Notification?",
"type": "main",
"index": 0
}
]
]
},
"π Route Updates to Platforms": {
"main": [
[
{
"node": "ποΈ Update Shopify",
"type": "main",
"index": 0
}
],
[
{
"node": "π Update WooCommerce",
"type": "main",
"index": 0
}
]
]
},
"ποΈ Fetch Shopify Inventory": {
"main": [
[
{
"node": "π Merge All Platform Data",
"type": "main",
"index": 0
}
]
]
},
"π Fetch WooCommerce Inventory": {
"main": [
[
{
"node": "π Merge All Platform Data",
"type": "main",
"index": 1
}
]
]
},
"β° Hourly Reconciliation Trigger": {
"main": [
[
{
"node": "π Parse Trigger Data",
"type": "main",
"index": 0
}
]
]
},
"π Compare & Identify Discrepancies": {
"main": [
[
{
"node": "β Has Discrepancies?",
"type": "main",
"index": 0
}
]
]
},
"Verify Email (BillionVerify)": {
"main": [
[
{
"node": "IF deliverable",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable": {
"main": [
[
{
"node": "βοΈ Send Alert Email",
"type": "main",
"index": 0
}
],
[]
]
}
},
"settings": {
"executionOrder": "v1"
}
}When to use this
- Cleaning a list before a Shopify send or sync.
- Protecting Shopify deliverability and sender reputation.
- Keeping bounce rates low so your sending is never throttled.
FAQ
Why verify before sending in Shopify?
Verifying first keeps your bounce rate low, which protects your sender reputation and your results.
How do I import this workflow?
Download the JSON, then in n8n go to Workflows β Import from File (or paste it). Install the BillionVerify community node and add your API key credential.
What happens to risky or catch-all addresses?
They are routed to the false branch and excluded from the send. You decide whether to retry, review, or drop them.
Add verification to your workflow
Create a free account, grab your API key, and stop bounces before they happen.
Get started free