{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "89d11b11-1157-45dd-8cb6-dbcfb8002d40",
      "name": "Sticky 00 Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -736,
        256
      ],
      "parameters": {
        "width": 620,
        "height": 1020,
        "content": "## Welcome! Start Here\n\nThis workflow checks for new Jumia orders and instantly notifies you via Telegram and WhatsApp.\n\nBefore activating the workflow:\n\n\u2022 Add your Jumia API credentials\n\u2022 Configure Telegram credentials\n\u2022 Configure WhatsApp Cloud API credentials\n\u2022 Create the Data Table\n\u2022 Test using Manual Trigger\n\u2022 Enable the Schedule Trigger when everything works\n\n\nAuthenticate\n        \u2193\nFetch Orders\n        \u2193\nNormalize Data\n        \u2193\nSkip Existing Orders\n        \u2193\nFormat Message\n        \u2193\nTelegram\n        \u2193\nWhatsApp\n        \u2193\nSave Processed Order"
      },
      "typeVersion": 1
    },
    {
      "id": "56992fa4-3444-4e6e-a13c-7755905a7bf1",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -16,
        800
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c168e143-633e-480c-b58f-271a55efd055",
      "name": "Poll Jumia Orders Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "disabled": true,
      "position": [
        -16,
        1008
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b8054a2f-37cb-41fb-a608-bda33d2db76b",
      "name": "Get Jumia Access Token",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        272,
        896
      ],
      "parameters": {
        "url": "https://vendor-api.jumia.com/token",
        "method": "POST",
        "options": {
          "timeout": 20000
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "client_id",
              "value": "REPLACE_WITH_JUMIA_CLIENT_ID"
            },
            {
              "name": "grant_type",
              "value": "refresh_token"
            },
            {
              "name": "refresh_token",
              "value": "REPLACE_WITH_JUMIA_REFRESH_TOKEN"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/x-www-form-urlencoded"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "bd32d35b-791f-491e-bf30-f66c4991ba99",
      "name": "Fetch Recent Jumia Orders",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        560,
        896
      ],
      "parameters": {
        "url": "https://vendor-api.jumia.com/orders",
        "options": {
          "timeout": 30000
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $json.access_token }}"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "589c8da7-7498-46c8-ab82-441671739360",
      "name": "Normalize Orders",
      "type": "n8n-nodes-base.code",
      "position": [
        848,
        896
      ],
      "parameters": {
        "jsCode": "const response = $json;\nconst now = new Date().toISOString();\nconst sellerId = 'REPLACE_WITH_SELLER_ID';\nconst marketplace = 'jumia';\nconst orders = Array.isArray(response.orders) ? response.orders : [];\nfunction valueOrFallback(value, fallback = '') {\n  return value === undefined || value === null || value === '' ? fallback : value;\n}\nfunction toNumber(value) {\n  const number = Number(valueOrFallback(value, 0));\n  return Number.isFinite(number) ? number : 0;\n}\nfunction toBoolean(value) {\n  if (typeof value === 'boolean') return value;\n  if (typeof value === 'string') return value.toLowerCase() === 'true';\n  return Boolean(value);\n}\nfunction buildCustomerName(address = {}) {\n  return [address.firstName, address.lastName].filter(Boolean).join(' ') || 'Unknown customer';\n}\nfunction buildShippingAddress(address = {}) {\n  return [address.address, address.ward, address.city, address.region, address.postalCode, address.countryName].filter(Boolean).join(', ');\n}\nfunction normalizeOrder(order) {\n  const orderId = String(valueOrFallback(order.number, order.id)).trim();\n  if (!orderId) return null;\n  const amount = order.totalAmountLocal || order.totalAmount || {};\n  const country = order.country || {};\n  const shipping = order.shippingAddress || {};\n  return {\n    seller_id: sellerId,\n    marketplace,\n    event_type: 'ORDER_CREATED',\n    received_at: now,\n    order_id: orderId,\n    jumia_order_id: order.id || '',\n    order: {\n      order_id: orderId,\n      jumia_order_id: order.id || '',\n      shop_ids: Array.isArray(order.shopIds) ? order.shopIds.join(', ') : '',\n      date: valueOrFallback(order.createdAt, now),\n      updated_at: valueOrFallback(order.updatedAt, ''),\n      pending_since: valueOrFallback(order.pendingSince, ''),\n      status: valueOrFallback(order.status, 'unknown'),\n      delivery_option: valueOrFallback(order.deliveryOption, ''),\n      total_items: toNumber(order.totalItems),\n      packed_items: toNumber(order.packedItems),\n      is_prepayment: toBoolean(order.isPrepayment),\n      has_multiple_status: toBoolean(order.hasMultipleStatus),\n      has_items_fulfilled_by_jumia: toBoolean(order.hasItemsFulfilledByJumia),\n      customer_name: buildCustomerName(shipping),\n      total: toNumber(amount.value),\n      currency: valueOrFallback(amount.currency, country.currencyCode || 'NGN'),\n      country_code: valueOrFallback(country.code, ''),\n      country_name: valueOrFallback(country.name, ''),\n      shipping_city: valueOrFallback(shipping.city, ''),\n      shipping_region: valueOrFallback(shipping.region, ''),\n      shipping_address: buildShippingAddress(shipping)\n    },\n    raw_order: order\n  };\n}\nreturn orders.map(normalizeOrder).filter(Boolean).map(order => ({ json: order }));"
      },
      "typeVersion": 2
    },
    {
      "id": "6c9ed865-a698-495c-ae56-3fe58d521eb8",
      "name": "Check Order Not Processed",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        1184,
        896
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "order_id",
              "keyValue": "={{ $json.order_id }}"
            }
          ]
        },
        "operation": "rowNotExists",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "jumia_processed_orders",
          "cachedResultName": "jumia_processed_orders"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "cd234ca8-2436-423e-9ca8-5444be178ece",
      "name": "Format Notification Message",
      "type": "n8n-nodes-base.code",
      "position": [
        1520,
        896
      ],
      "parameters": {
        "jsCode": "const ctx = $json;\nconst order = ctx.order;\nfunction formatMoney(value, currency) {\n  try {\n    return new Intl.NumberFormat('en', { style: 'currency', currency, maximumFractionDigits: 0 }).format(Number(value || 0));\n  } catch (error) {\n    return String(value || 0) + ' ' + String(currency || '').trim();\n  }\n}\nconst message = [\n  'New Jumia Order',\n  '',\n  'Order: ' + order.order_id,\n  'Status: ' + order.status,\n  'Date: ' + order.date,\n  'Pending Since: ' + (order.pending_since || 'Not supplied'),\n  'Delivery: ' + (order.delivery_option || 'Not supplied'),\n  'Customer: ' + order.customer_name,\n  'Total: ' + formatMoney(order.total, order.currency),\n  'Items: ' + order.total_items,\n  'Packed Items: ' + order.packed_items,\n  'Prepayment: ' + (order.is_prepayment ? 'Yes' : 'No'),\n  'Fulfilled by Jumia: ' + (order.has_items_fulfilled_by_jumia ? 'Yes' : 'No'),\n  '',\n  'Shipping City: ' + (order.shipping_city || 'Not supplied'),\n  'Shipping Address: ' + (order.shipping_address || 'Not supplied'),\n  '',\n  'Please prepare this order for fulfillment.'\n].join('\\n');\nreturn [{ json: { ...ctx, notification_message: message, processed_at: new Date().toISOString() } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "5e2bb57c-25df-46ed-99f1-67123cedbdc5",
      "name": "Send a text message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1824,
        896
      ],
      "parameters": {
        "text": "={{ $json.notification_message }}",
        "chatId": "REPLACE_WITH_TELEGRAM_CHAT_ID",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "35a9cca8-72d0-417b-8f0f-a221a697c784",
      "name": "Send message",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        2112,
        896
      ],
      "parameters": {
        "textBody": "={{ $(\"Format Notification Message\").item.json.notification_message }}",
        "operation": "send",
        "phoneNumberId": "REPLACE_WITH_WHATSAPP_PHONE_NUMBER_ID",
        "additionalFields": {},
        "recipientPhoneNumber": "REPLACE_WITH_SELLER_WHATSAPP_NUMBER"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "2a6f58da-2cfa-4b8e-861d-5a48295f2fd2",
      "name": "Insert Processed Order",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        2400,
        896
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "notified",
            "order_id": "={{ $(\"Format Notification Message\").item.json.order_id }}",
            "seller_id": "={{ $(\"Format Notification Message\").item.json.seller_id }}",
            "first_seen": "={{ $(\"Format Notification Message\").item.json.received_at }}",
            "marketplace": "={{ $(\"Format Notification Message\").item.json.marketplace }}",
            "notified_at": "={{ $(\"Format Notification Message\").item.json.processed_at }}",
            "raw_payload": "={{ JSON.stringify($(\"Format Notification Message\").item.json.raw_order || {}) }}"
          },
          "schema": [
            {
              "id": "seller_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "seller_id",
              "defaultMatch": false
            },
            {
              "id": "marketplace",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "marketplace",
              "defaultMatch": false
            },
            {
              "id": "order_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "order_id",
              "defaultMatch": false
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false
            },
            {
              "id": "first_seen",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "first_seen",
              "defaultMatch": false
            },
            {
              "id": "notified_at",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "notified_at",
              "defaultMatch": false
            },
            {
              "id": "raw_payload",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "raw_payload",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "jumia_processed_orders",
          "cachedResultName": "jumia_processed_orders"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "2b9c710c-f6bd-4449-a30d-b760067ad737",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        256
      ],
      "parameters": {
        "color": "#5D6569",
        "width": 288,
        "height": 1024,
        "content": "## Polling\n\nThe Schedule Trigger is disabled by default.\n\nAfter configuration:\n\n\u2713 Enable the trigger\n\u2713 Adjust the polling interval if needed\n\nDefault interval:\nEvery 5 minutes"
      },
      "typeVersion": 1
    },
    {
      "id": "3969fc0c-dac7-4400-8608-d3b72c1d6add",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        256
      ],
      "parameters": {
        "color": "#5D6569",
        "width": 256,
        "height": 1024,
        "content": "## Configure Jumia API\n\nReplace the following values:\n\n\u2022 REPLACE_WITH_JUMIA_CLIENT_ID\n\u2022 REPLACE_WITH_JUMIA_REFRESH_TOKEN\n\u2022 REPLACE_WITH_SELLER_ID\n\nThese are used to authenticate against the Jumia Vendor API."
      },
      "typeVersion": 1
    },
    {
      "id": "777e1bf0-4d7e-4807-a6e7-f717e96b4f47",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        256
      ],
      "parameters": {
        "color": "#5D6569",
        "width": 384,
        "height": 1024,
        "content": "## Duplicate Protection\n\nCreate an n8n Data Table named:\n\njumia_processed_orders\n\nRequired columns:\n\nseller_id\nmarketplace\norder_id\nstatus\nfirst_seen\nnotified_at\nraw_payload\n\nThis prevents duplicate notifications."
      },
      "typeVersion": 1
    },
    {
      "id": "c0fa1db6-d1e5-45b1-b888-5e5b86433b79",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        256
      ],
      "parameters": {
        "color": "#5D6569",
        "width": 528,
        "height": 1024,
        "content": "## Notification Setup\n\nTelegram\n\n\u2022 Connect Telegram credentials\n\u2022 Replace REPLACE_WITH_TELEGRAM_CHAT_ID\n\nWhatsApp\n\n\u2022 Connect Meta Cloud API credentials\n\u2022 Replace:\n  - Phone Number ID\n  - Seller WhatsApp Number"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Send message": {
      "main": [
        [
          {
            "node": "Insert Processed Order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Get Jumia Access Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Orders": {
      "main": [
        [
          {
            "node": "Check Order Not Processed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a text message": {
      "main": [
        [
          {
            "node": "Send message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Jumia Access Token": {
      "main": [
        [
          {
            "node": "Fetch Recent Jumia Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Order Not Processed": {
      "main": [
        [
          {
            "node": "Format Notification Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Recent Jumia Orders": {
      "main": [
        [
          {
            "node": "Normalize Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Notification Message": {
      "main": [
        [
          {
            "node": "Send a text message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Jumia Orders Every 5 Minutes": {
      "main": [
        [
          {
            "node": "Get Jumia Access Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}