{
  "name": "Nodbot - Shopify order webhook to CRM with idempotency",
  "nodes": [
    {
      "parameters": {
        "path": "integration-shopify-n8n-order-webhook",
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "id": "123fdeef-294a-4989-873f-749c786aa48b",
      "name": "Shopify Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst headers = $json.headers ?? {};\nconst topic = String(headers['x-shopify-topic'] ?? src.topic ?? '').trim();\nconst shop = String(headers['x-shopify-shop-domain'] ?? src.shop_domain ?? '').trim().toLowerCase();\nconst allowedTopics = ['orders/create', 'orders/paid', 'orders/fulfilled', 'orders/cancelled'];\nconst allowedShops = ['demo-store.myshopify.com'];\nif (!allowedTopics.includes(topic)) return [{ json: { action: 'ignore', reason: 'topic_not_allowed', topic } }];\nif (!allowedShops.includes(shop)) throw new Error(`Shopify shop is not allowed: ${shop}`);\nconst orderId = String(src.id ?? src.admin_graphql_api_id ?? '').trim();\nif (!orderId) throw new Error('No Shopify order id');\nconst lines = Array.isArray(src.line_items) ? src.line_items.map(item => ({\n  sku: String(item.sku ?? '').trim(),\n  name: String(item.name ?? '').trim(),\n  quantity: Number(item.quantity ?? 0),\n  price: Number(item.price ?? 0)\n})) : [];\nreturn [{ json: {\n  action: 'sync_order',\n  idempotency_key: `shopify:${shop}:${topic}:${orderId}`,\n  shop,\n  topic,\n  order_id: orderId,\n  order_name: String(src.name ?? '').trim(),\n  customer_email: String(src.email ?? src.customer?.email ?? '').trim().toLowerCase(),\n  customer_phone: String(src.customer?.phone ?? src.phone ?? '').trim(),\n  total_price: Number(src.total_price ?? 0),\n  currency: String(src.currency ?? '').trim(),\n  financial_status: String(src.financial_status ?? '').trim(),\n  fulfillment_status: src.fulfillment_status ?? 'not_fulfilled',\n  line_items: lines,\n  crm_comment: `Shopify ${topic} ${src.name ?? orderId}`\n}}];"
      },
      "id": "27e7ffde-61e7-47dc-b6f6-a1502221b8c5",
      "name": "Validate Shopify event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        280,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst headers = $json.headers ?? {};\nconst topic = String(headers['x-shopify-topic'] ?? src.topic ?? '').trim();\nconst shop = String(headers['x-shopify-shop-domain'] ?? src.shop_domain ?? '').trim().toLowerCase();\nconst allowedTopics = ['orders/create', 'orders/paid', 'orders/fulfilled', 'orders/cancelled'];\nconst allowedShops = ['demo-store.myshopify.com'];\nif (!allowedTopics.includes(topic)) return [{ json: { action: 'ignore', reason: 'topic_not_allowed', topic } }];\nif (!allowedShops.includes(shop)) throw new Error(`Shopify shop is not allowed: ${shop}`);\nconst orderId = String(src.id ?? src.admin_graphql_api_id ?? '').trim();\nif (!orderId) throw new Error('No Shopify order id');\nconst lines = Array.isArray(src.line_items) ? src.line_items.map(item => ({\n  sku: String(item.sku ?? '').trim(),\n  name: String(item.name ?? '').trim(),\n  quantity: Number(item.quantity ?? 0),\n  price: Number(item.price ?? 0)\n})) : [];\nreturn [{ json: {\n  action: 'sync_order',\n  idempotency_key: `shopify:${shop}:${topic}:${orderId}`,\n  shop,\n  topic,\n  order_id: orderId,\n  order_name: String(src.name ?? '').trim(),\n  customer_email: String(src.email ?? src.customer?.email ?? '').trim().toLowerCase(),\n  customer_phone: String(src.customer?.phone ?? src.phone ?? '').trim(),\n  total_price: Number(src.total_price ?? 0),\n  currency: String(src.currency ?? '').trim(),\n  financial_status: String(src.financial_status ?? '').trim(),\n  fulfillment_status: src.fulfillment_status ?? 'not_fulfilled',\n  line_items: lines,\n  crm_comment: `Shopify ${topic} ${src.name ?? orderId}`\n}}];"
      },
      "id": "5c3375b4-3377-40ca-bbdf-b5bc12c34206",
      "name": "Normalize order",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        260
      ]
    },
    {
      "parameters": {},
      "id": "a9ea3d46-21db-420c-9739-d77cbe166cb8",
      "name": "Check idempotency",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        840,
        260
      ]
    },
    {
      "parameters": {},
      "id": "9b0aab44-01e8-4ab3-8327-dbbb0e71c096",
      "name": "Update CRM or ERP",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 2,
      "position": [
        1120,
        260
      ]
    },
    {
      "parameters": {},
      "id": "9b9e91e9-5bb6-4056-8a7a-11ca6201cc5c",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 2,
      "position": [
        1400,
        260
      ]
    }
  ],
  "connections": {
    "Shopify Webhook": {
      "main": [
        [
          {
            "node": "Validate Shopify event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Shopify event": {
      "main": [
        [
          {
            "node": "Normalize order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize order": {
      "main": [
        [
          {
            "node": "Check idempotency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check idempotency": {
      "main": [
        [
          {
            "node": "Update CRM or ERP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update CRM or ERP": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    "nodbot",
    "integration",
    "production"
  ],
  "triggerCount": 1,
  "updatedAt": "2026-05-30T00:00:00.000Z",
  "versionId": "c98cd226-f2e7-4dcc-b4bd-3b788a9c1d60",
  "meta": {
    "templateCredsSetupCompleted": false
  }
}