n8n Automation

n8n E-commerce Automation: Shopify Integration and Order Management Workflows

DeviDevs Team
11 min read
#n8n#Shopify#e-commerce#order automation#workflow automation

E-commerce automation transforms manual order processing into scalable, error-free workflows. This guide covers building production-ready Shopify automations with n8n for order management, inventory control, and customer engagement.

Shopify Integration Setup

API Configuration

// Shopify API Configuration
// Store in n8n credentials
{
  "shopifyApi": {
    "apiKey": "{{ $env.SHOPIFY_API_KEY }}",
    "password": "{{ $env.SHOPIFY_API_PASSWORD }}",
    "shopSubdomain": "{{ $env.SHOPIFY_STORE_NAME }}",
    "apiVersion": "2024-01"
  }
}
 
// Required API Scopes:
// - read_orders, write_orders
// - read_products, write_products
// - read_inventory, write_inventory
// - read_customers, write_customers
// - read_fulfillments, write_fulfillments

Webhook Configuration

// Function Node: Shopify Webhook Handler
const webhookData = $input.first().json;
const topic = $input.first().headers['x-shopify-topic'];
const hmac = $input.first().headers['x-shopify-hmac-sha256'];
 
// Verify webhook signature
const crypto = require('crypto');
const secret = $env.SHOPIFY_WEBHOOK_SECRET;
const body = JSON.stringify(webhookData);
 
const calculatedHmac = crypto
  .createHmac('sha256', secret)
  .update(body, 'utf8')
  .digest('base64');
 
if (hmac !== calculatedHmac) {
  throw new Error('Invalid webhook signature');
}
 
// Parse webhook topic
const [resource, action] = topic.split('/');
 
return [{
  json: {
    resource,
    action,
    data: webhookData,
    receivedAt: new Date().toISOString(),
    verified: true
  }
}];

Order Processing Workflows

New Order Handler

// Function Node: Process New Order
const order = $input.first().json.data;
 
// Extract order details
const orderData = {
  orderId: order.id,
  orderNumber: order.order_number,
  orderName: order.name,
  createdAt: order.created_at,
 
  // Customer info
  customer: {
    id: order.customer?.id,
    email: order.customer?.email || order.email,
    firstName: order.customer?.first_name,
    lastName: order.customer?.last_name,
    phone: order.customer?.phone || order.phone,
    totalOrders: order.customer?.orders_count || 1,
    totalSpent: order.customer?.total_spent || order.total_price
  },
 
  // Order totals
  financials: {
    subtotal: parseFloat(order.subtotal_price),
    totalTax: parseFloat(order.total_tax),
    totalShipping: parseFloat(order.total_shipping_price_set?.shop_money?.amount || 0),
    totalDiscount: parseFloat(order.total_discounts),
    totalPrice: parseFloat(order.total_price),
    currency: order.currency
  },
 
  // Line items
  items: order.line_items.map(item => ({
    id: item.id,
    productId: item.product_id,
    variantId: item.variant_id,
    title: item.title,
    variantTitle: item.variant_title,
    sku: item.sku,
    quantity: item.quantity,
    price: parseFloat(item.price),
    totalDiscount: parseFloat(item.total_discount),
    requiresShipping: item.requires_shipping,
    fulfillmentStatus: item.fulfillment_status
  })),
 
  // Shipping info
  shipping: {
    method: order.shipping_lines[0]?.title,
    address: order.shipping_address ? {
      name: `${order.shipping_address.first_name} ${order.shipping_address.last_name}`,
      company: order.shipping_address.company,
      address1: order.shipping_address.address1,
      address2: order.shipping_address.address2,
      city: order.shipping_address.city,
      province: order.shipping_address.province,
      country: order.shipping_address.country,
      zip: order.shipping_address.zip,
      phone: order.shipping_address.phone
    } : null
  },
 
  // Flags
  flags: {
    isFirstOrder: (order.customer?.orders_count || 1) === 1,
    hasDiscount: parseFloat(order.total_discounts) > 0,
    isPaid: order.financial_status === 'paid',
    requiresShipping: order.line_items.some(item => item.requires_shipping),
    hasFraudRisk: order.fraud_analysis?.risk_level === 'high'
  },
 
  // Discount codes
  discountCodes: order.discount_codes || [],
 
  // Tags and notes
  tags: order.tags ? order.tags.split(',').map(t => t.trim()) : [],
  note: order.note,
  noteAttributes: order.note_attributes
};
 
return [{ json: orderData }];

Order Routing Logic

// Function Node: Route Order by Criteria
const order = $input.first().json;
 
// Determine fulfillment route
let route = 'standard';
let priority = 'normal';
let warehouse = 'main';
 
// High value orders
if (order.financials.totalPrice > 500) {
  priority = 'high';
}
 
// First-time customer
if (order.flags.isFirstOrder) {
  order.flags.requiresWelcomeEmail = true;
}
 
// Fraud risk
if (order.flags.hasFraudRisk) {
  route = 'manual_review';
  priority = 'hold';
}
 
// Express shipping
const shippingMethod = order.shipping.method?.toLowerCase() || '';
if (shippingMethod.includes('express') || shippingMethod.includes('overnight')) {
  priority = 'urgent';
}
 
// International orders
if (order.shipping.address?.country !== 'United States') {
  route = 'international';
  warehouse = 'international';
}
 
// Specific product routing
const requiresSpecialHandling = order.items.some(item =>
  item.tags?.includes('fragile') ||
  item.tags?.includes('hazmat') ||
  item.sku?.startsWith('SPECIAL-')
);
 
if (requiresSpecialHandling) {
  route = 'special_handling';
}
 
return [{
  json: {
    ...order,
    routing: {
      route,
      priority,
      warehouse,
      assignedAt: new Date().toISOString()
    }
  }
}];

Inventory Management

Real-Time Inventory Sync

// Function Node: Inventory Level Handler
const inventoryUpdate = $input.first().json;
 
// Process inventory level change
const inventoryData = {
  inventoryItemId: inventoryUpdate.inventory_item_id,
  locationId: inventoryUpdate.location_id,
  available: inventoryUpdate.available,
  updatedAt: inventoryUpdate.updated_at
};
 
// Check for low stock threshold
const lowStockThreshold = 10;
const criticalStockThreshold = 3;
 
let stockStatus = 'normal';
let alertRequired = false;
 
if (inventoryData.available <= 0) {
  stockStatus = 'out_of_stock';
  alertRequired = true;
} else if (inventoryData.available <= criticalStockThreshold) {
  stockStatus = 'critical';
  alertRequired = true;
} else if (inventoryData.available <= lowStockThreshold) {
  stockStatus = 'low';
  alertRequired = true;
}
 
return [{
  json: {
    ...inventoryData,
    stockStatus,
    alertRequired,
    thresholds: {
      low: lowStockThreshold,
      critical: criticalStockThreshold
    }
  }
}];

Multi-Location Inventory Aggregation

// Function Node: Aggregate Inventory Across Locations
const inventoryLevels = $input.all().map(item => item.json);
 
// Group by inventory item
const aggregated = {};
 
for (const level of inventoryLevels) {
  const itemId = level.inventory_item_id;
 
  if (!aggregated[itemId]) {
    aggregated[itemId] = {
      inventoryItemId: itemId,
      totalAvailable: 0,
      totalOnHand: 0,
      locations: [],
      lastUpdated: level.updated_at
    };
  }
 
  aggregated[itemId].totalAvailable += level.available || 0;
  aggregated[itemId].totalOnHand += level.on_hand || 0;
  aggregated[itemId].locations.push({
    locationId: level.location_id,
    locationName: level.location_name,
    available: level.available,
    onHand: level.on_hand,
    incoming: level.incoming || 0
  });
 
  // Track most recent update
  if (level.updated_at > aggregated[itemId].lastUpdated) {
    aggregated[itemId].lastUpdated = level.updated_at;
  }
}
 
// Convert to array and calculate status
const results = Object.values(aggregated).map(item => ({
  ...item,
  status: item.totalAvailable <= 0 ? 'out_of_stock' :
          item.totalAvailable <= 10 ? 'low_stock' : 'in_stock',
  needsReorder: item.totalAvailable <= 20
}));
 
return results.map(item => ({ json: item }));

Automated Reorder Workflow

// Function Node: Generate Purchase Order
const lowStockItems = $input.all().map(item => item.json);
 
// Group items by supplier
const bySupplier = {};
 
for (const item of lowStockItems) {
  const supplier = item.supplier || 'default';
 
  if (!bySupplier[supplier]) {
    bySupplier[supplier] = {
      supplier,
      items: [],
      totalItems: 0,
      estimatedCost: 0
    };
  }
 
  // Calculate reorder quantity
  const reorderPoint = item.reorderPoint || 20;
  const reorderQuantity = item.reorderQuantity || 50;
  const currentStock = item.totalAvailable;
  const quantityToOrder = Math.max(0, reorderQuantity - currentStock + reorderPoint);
 
  if (quantityToOrder > 0) {
    bySupplier[supplier].items.push({
      sku: item.sku,
      productTitle: item.productTitle,
      currentStock,
      reorderQuantity: quantityToOrder,
      unitCost: item.unitCost || 0,
      totalCost: quantityToOrder * (item.unitCost || 0)
    });
 
    bySupplier[supplier].totalItems += quantityToOrder;
    bySupplier[supplier].estimatedCost += quantityToOrder * (item.unitCost || 0);
  }
}
 
// Generate purchase orders
const purchaseOrders = Object.values(bySupplier)
  .filter(po => po.items.length > 0)
  .map((po, index) => ({
    ...po,
    poNumber: `PO-${Date.now()}-${index + 1}`,
    createdAt: new Date().toISOString(),
    status: 'draft',
    expectedDelivery: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
  }));
 
return purchaseOrders.map(po => ({ json: po }));

Customer Communication Automation

Order Confirmation Email

// Function Node: Generate Order Confirmation Email
const order = $input.first().json;
 
// Format line items for email
const itemsHtml = order.items.map(item => `
  <tr>
    <td style="padding: 10px; border-bottom: 1px solid #eee;">
      ${item.title}${item.variantTitle ? ` - ${item.variantTitle}` : ''}
    </td>
    <td style="padding: 10px; border-bottom: 1px solid #eee; text-align: center;">
      ${item.quantity}
    </td>
    <td style="padding: 10px; border-bottom: 1px solid #eee; text-align: right;">
      $${(item.price * item.quantity).toFixed(2)}
    </td>
  </tr>
`).join('');
 
// Format shipping address
const address = order.shipping.address;
const addressHtml = address ? `
  <p>
    ${address.name}<br>
    ${address.company ? address.company + '<br>' : ''}
    ${address.address1}<br>
    ${address.address2 ? address.address2 + '<br>' : ''}
    ${address.city}, ${address.province} ${address.zip}<br>
    ${address.country}
  </p>
` : '<p>Digital delivery - no shipping required</p>';
 
const emailHtml = `
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }
    .header { background: #2563eb; color: white; padding: 20px; text-align: center; }
    .content { padding: 20px; }
    table { width: 100%; border-collapse: collapse; }
    th { background: #f3f4f6; padding: 10px; text-align: left; }
    .totals { margin-top: 20px; text-align: right; }
    .footer { background: #f9fafb; padding: 20px; text-align: center; font-size: 12px; }
  </style>
</head>
<body>
  <div class="header">
    <h1>Order Confirmed!</h1>
    <p>Order ${order.orderName}</p>
  </div>
 
  <div class="content">
    <p>Hi ${order.customer.firstName || 'there'},</p>
    <p>Thank you for your order! We've received it and are getting it ready.</p>
 
    <h3>Order Details</h3>
    <table>
      <thead>
        <tr>
          <th>Item</th>
          <th style="text-align: center;">Qty</th>
          <th style="text-align: right;">Price</th>
        </tr>
      </thead>
      <tbody>
        ${itemsHtml}
      </tbody>
    </table>
 
    <div class="totals">
      <p>Subtotal: $${order.financials.subtotal.toFixed(2)}</p>
      ${order.financials.totalDiscount > 0 ? `<p>Discount: -$${order.financials.totalDiscount.toFixed(2)}</p>` : ''}
      <p>Shipping: $${order.financials.totalShipping.toFixed(2)}</p>
      <p>Tax: $${order.financials.totalTax.toFixed(2)}</p>
      <p><strong>Total: $${order.financials.totalPrice.toFixed(2)} ${order.financials.currency}</strong></p>
    </div>
 
    <h3>Shipping Address</h3>
    ${addressHtml}
 
    <p>You'll receive another email when your order ships with tracking information.</p>
  </div>
 
  <div class="footer">
    <p>Questions? Reply to this email or contact us at support@example.com</p>
  </div>
</body>
</html>
`;
 
return [{
  json: {
    to: order.customer.email,
    subject: `Order Confirmed - ${order.orderName}`,
    html: emailHtml,
    tags: ['order-confirmation', `order-${order.orderId}`]
  }
}];

Shipping Notification

// Function Node: Generate Shipping Notification
const fulfillment = $input.first().json;
 
const trackingUrl = fulfillment.tracking_url ||
  (fulfillment.tracking_company === 'USPS' ?
    `https://tools.usps.com/go/TrackConfirmAction?tLabels=${fulfillment.tracking_number}` :
  fulfillment.tracking_company === 'UPS' ?
    `https://www.ups.com/track?tracknum=${fulfillment.tracking_number}` :
  fulfillment.tracking_company === 'FedEx' ?
    `https://www.fedex.com/fedextrack/?trknbr=${fulfillment.tracking_number}` :
    null);
 
const emailHtml = `
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }
    .header { background: #059669; color: white; padding: 20px; text-align: center; }
    .content { padding: 20px; }
    .tracking-box { background: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0; text-align: center; }
    .track-button { display: inline-block; background: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin-top: 10px; }
    .footer { background: #f9fafb; padding: 20px; text-align: center; font-size: 12px; }
  </style>
</head>
<body>
  <div class="header">
    <h1>📦 Your Order Has Shipped!</h1>
  </div>
 
  <div class="content">
    <p>Great news! Your order is on its way.</p>
 
    <div class="tracking-box">
      <p><strong>Carrier:</strong> ${fulfillment.tracking_company || 'Standard Shipping'}</p>
      ${fulfillment.tracking_number ? `<p><strong>Tracking Number:</strong> ${fulfillment.tracking_number}</p>` : ''}
      ${trackingUrl ? `<a href="${trackingUrl}" class="track-button">Track Your Package</a>` : ''}
    </div>
 
    <h3>Items Shipped</h3>
    <ul>
      ${fulfillment.line_items.map(item =>
        `<li>${item.title}${item.variant_title ? ` - ${item.variant_title}` : ''} (x${item.quantity})</li>`
      ).join('')}
    </ul>
 
    <p>Estimated delivery: ${fulfillment.estimated_delivery_at ?
      new Date(fulfillment.estimated_delivery_at).toLocaleDateString() :
      '3-5 business days'}</p>
  </div>
 
  <div class="footer">
    <p>Questions about your shipment? Contact us at support@example.com</p>
  </div>
</body>
</html>
`;
 
return [{
  json: {
    to: fulfillment.customer_email,
    subject: 'Your Order Has Shipped! 📦',
    html: emailHtml,
    tags: ['shipping-notification', `order-${fulfillment.order_id}`]
  }
}];

Fulfillment Automation

Fulfillment Service Integration

// Function Node: Create Fulfillment Request
const order = $input.first().json;
 
// Prepare fulfillment request for shipping carrier
const fulfillmentRequest = {
  orderId: order.orderId,
  orderNumber: order.orderNumber,
 
  shipTo: {
    name: order.shipping.address.name,
    company: order.shipping.address.company,
    street1: order.shipping.address.address1,
    street2: order.shipping.address.address2,
    city: order.shipping.address.city,
    state: order.shipping.address.province,
    postalCode: order.shipping.address.zip,
    country: order.shipping.address.country,
    phone: order.shipping.address.phone
  },
 
  shipFrom: {
    name: 'Your Store',
    street1: '123 Warehouse St',
    city: 'Commerce City',
    state: 'CA',
    postalCode: '90210',
    country: 'United States',
    phone: '555-123-4567'
  },
 
  packages: [{
    weight: order.items.reduce((sum, item) =>
      sum + (item.weight || 0.5) * item.quantity, 0
    ),
    weightUnit: 'lb',
    dimensions: {
      length: 10,
      width: 8,
      height: 4,
      unit: 'in'
    }
  }],
 
  items: order.items.map(item => ({
    sku: item.sku,
    description: item.title,
    quantity: item.quantity,
    value: item.price,
    weight: item.weight || 0.5
  })),
 
  serviceType: mapShippingService(order.shipping.method),
  shipDate: new Date().toISOString().split('T')[0],
  reference: order.orderNumber,
 
  options: {
    signatureRequired: order.financials.totalPrice > 100,
    insurance: order.financials.totalPrice > 200 ? {
      amount: order.financials.totalPrice,
      currency: order.financials.currency
    } : null
  }
};
 
function mapShippingService(shopifyMethod) {
  const mappings = {
    'Standard Shipping': 'ground',
    'Express Shipping': 'express',
    'Overnight': 'overnight',
    'Free Shipping': 'ground'
  };
  return mappings[shopifyMethod] || 'ground';
}
 
return [{ json: fulfillmentRequest }];

Update Shopify Fulfillment

// Function Node: Update Shopify with Fulfillment
const carrierResponse = $input.first().json;
const originalOrder = $input.first().json.orderData;
 
// Build Shopify fulfillment payload
const fulfillmentPayload = {
  fulfillment: {
    location_id: $env.SHOPIFY_LOCATION_ID,
    tracking_number: carrierResponse.trackingNumber,
    tracking_company: carrierResponse.carrier,
    tracking_url: carrierResponse.trackingUrl,
    notify_customer: true,
    line_items: originalOrder.items.map(item => ({
      id: item.id,
      quantity: item.quantity
    }))
  }
};
 
return [{
  json: {
    endpoint: `/admin/api/2024-01/orders/${originalOrder.orderId}/fulfillments.json`,
    method: 'POST',
    payload: fulfillmentPayload
  }
}];

Analytics and Reporting

Daily Sales Summary

// Function Node: Generate Daily Sales Report
const orders = $input.all().map(item => item.json);
 
// Calculate metrics
const metrics = {
  date: new Date().toISOString().split('T')[0],
  totalOrders: orders.length,
  totalRevenue: 0,
  averageOrderValue: 0,
  totalItems: 0,
  newCustomers: 0,
  returningCustomers: 0,
  topProducts: {},
  revenueByHour: new Array(24).fill(0),
  ordersByCountry: {}
};
 
for (const order of orders) {
  // Revenue
  metrics.totalRevenue += order.financials.totalPrice;
 
  // Items
  metrics.totalItems += order.items.reduce((sum, item) => sum + item.quantity, 0);
 
  // Customer type
  if (order.flags.isFirstOrder) {
    metrics.newCustomers++;
  } else {
    metrics.returningCustomers++;
  }
 
  // Product popularity
  for (const item of order.items) {
    const key = item.sku || item.title;
    if (!metrics.topProducts[key]) {
      metrics.topProducts[key] = { title: item.title, quantity: 0, revenue: 0 };
    }
    metrics.topProducts[key].quantity += item.quantity;
    metrics.topProducts[key].revenue += item.price * item.quantity;
  }
 
  // Revenue by hour
  const hour = new Date(order.createdAt).getHours();
  metrics.revenueByHour[hour] += order.financials.totalPrice;
 
  // Geography
  const country = order.shipping.address?.country || 'Unknown';
  metrics.ordersByCountry[country] = (metrics.ordersByCountry[country] || 0) + 1;
}
 
// Calculate averages
metrics.averageOrderValue = metrics.totalOrders > 0 ?
  metrics.totalRevenue / metrics.totalOrders : 0;
 
// Sort top products
metrics.topProducts = Object.entries(metrics.topProducts)
  .sort((a, b) => b[1].revenue - a[1].revenue)
  .slice(0, 10)
  .map(([sku, data]) => ({ sku, ...data }));
 
return [{ json: metrics }];

Error Handling and Monitoring

Workflow Error Handler

// Function Node: E-commerce Error Handler
const error = $input.first().json.error;
const context = $input.first().json.context;
 
// Categorize error
let category = 'unknown';
let severity = 'medium';
let action = 'log';
 
const errorMessage = error.message?.toLowerCase() || '';
 
if (errorMessage.includes('rate limit') || errorMessage.includes('429')) {
  category = 'rate_limit';
  severity = 'low';
  action = 'retry_with_backoff';
} else if (errorMessage.includes('not found') || errorMessage.includes('404')) {
  category = 'not_found';
  severity = 'medium';
  action = 'skip';
} else if (errorMessage.includes('unauthorized') || errorMessage.includes('401')) {
  category = 'auth';
  severity = 'high';
  action = 'alert';
} else if (errorMessage.includes('inventory') || errorMessage.includes('stock')) {
  category = 'inventory';
  severity = 'high';
  action = 'manual_review';
} else if (errorMessage.includes('payment')) {
  category = 'payment';
  severity = 'critical';
  action = 'alert_urgent';
}
 
const errorReport = {
  timestamp: new Date().toISOString(),
  category,
  severity,
  action,
  error: {
    message: error.message,
    code: error.code,
    stack: error.stack?.slice(0, 500)
  },
  context: {
    orderId: context?.orderId,
    operation: context?.operation,
    workflowId: $workflow.id,
    executionId: $execution.id
  }
};
 
return [{
  json: {
    ...errorReport,
    shouldRetry: action.includes('retry'),
    shouldAlert: action.includes('alert'),
    alertChannel: severity === 'critical' ? 'pagerduty' : 'slack'
  }
}];

Best Practices Summary

  1. Validate webhooks using HMAC signatures
  2. Handle rate limits with exponential backoff
  3. Use idempotency keys to prevent duplicate processing
  4. Monitor inventory in real-time across locations
  5. Personalize communications based on customer history
  6. Implement error handling with appropriate escalation
  7. Track analytics for business insights
  8. Test thoroughly before going live

E-commerce automation with n8n enables scaling operations while maintaining customer experience quality. Start with core order processing and expand to inventory and marketing automation as you grow.

Weekly AI Security & Automation Digest

Get the latest on AI Security, workflow automation, secure integrations, and custom platform development delivered weekly.

No spam. Unsubscribe anytime.