n8n Automation

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

Petru Constantin
--11 min lectura
#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.

Ai nevoie de ajutor cu conformitatea EU AI Act sau securitatea AI?

Programeaza o consultatie gratuita de 30 de minute. Fara obligatii.

Programeaza un Apel

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.