{
  "nodes": [
    {
      "id": "df8fb435-f49e-4dec-8ed5-0b5b536ceab3",
      "name": "Store to SD & Pass token",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        0
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst r = $input.first().json || {};\nconst now = Date.now();\nsd.zalo = sd.zalo || {};\nif (r.access_token) sd.zalo.access_token = r.access_token;\nif (r.refresh_token) sd.zalo.refresh_token = r.refresh_token;\nconst exp = parseInt(r.expires_in, 10);\nif (!isNaN(exp)) sd.zalo.access_expires_at = now + exp * 1000;\nconst rExp = parseInt(r.refresh_token_expires_in, 10);\nif (!isNaN(rExp)) sd.zalo.refresh_expires_at = now + rExp * 1000;\n// IMPORTANT: return the refreshed token in the CURRENT ITEM\nreturn [{ json: {\n  source: \"refreshed\",\n  access_token: sd.zalo.access_token,\n  access_expires_at: sd.zalo.access_expires_at\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "5676141d-7a9e-4872-900a-48d7a7c59cc2",
      "name": "Refresh Token (Zalo v4)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        64,
        0
      ],
      "parameters": {
        "url": "https://oauth.zaloapp.com/v4/oa/access_token",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "refresh_token",
              "value": "={{ $json.refresh_token }}"
            },
            {
              "name": "app_id",
              "value": "={{ $json.app_id }}"
            },
            {
              "name": "grant_type",
              "value": "refresh_token"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "secret_key",
              "value": "={{ $('Set Refresh Token and App ID').item.json.secret_key }}"
            }
          ]
        }
      },
      "typeVersion": 3
    },
    {
      "id": "65bbbd29-24ae-475d-80d2-674da63db2eb",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -848,
        16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 12
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "62cbb107-ef08-4b09-b061-ecb9ae02c626",
      "name": "Execute_Node",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -848,
        -176
      ],
      "parameters": {
        "path": "99397651-be96-4106-9017-3e62f0ddf03e",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "21029f3c-0080-4950-80f5-965cc4259239",
      "name": "Clean Zalo Static Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -608,
        -176
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\ndelete sd['zalo'];\nreturn [{ json: { cleared: true } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d80a21bf-9e3a-4125-9657-38520907f78a",
      "name": "Set Refresh Token and App ID",
      "type": "n8n-nodes-base.set",
      "position": [
        -384,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "125b6083-9cac-4c88-b1c9-968cfe06a5d3",
              "name": "refresh_token",
              "type": "string",
              "value": "refresh_token_initial_refesh_token"
            },
            {
              "id": "d8e706c2-9609-4780-95d7-e3c5297a0cee",
              "name": "app_id",
              "type": "string",
              "value": "your_oa_app_id"
            },
            {
              "id": "8230a16f-3100-4fdb-9125-317f1c63fd23",
              "name": "secret_key",
              "type": "string",
              "value": "your_app_secret_key"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "37a79c0a-08c2-4a9b-9655-aa2e49c2f36c",
      "name": "Load to Static Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -176,
        0
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst bufferMs = 90 * 1000; // refresh 90s early\nsd.zalo =    sd.zalo || {};\nconsole.log('new code #### here this is',sd.zalo);\n// Seed refresh token on very first run\nif (!sd.zalo.refresh_token) {\n  sd.zalo.refresh_token =  $input.first().json.refresh_token;\n}\n\nconst hasAccess = !!sd.zalo.access_token;\nconst notExpired = !!sd.zalo.access_expires_at && (sd.zalo.access_expires_at - bufferMs) > now;\nconst needs_refresh = !(hasAccess && notExpired);\n\nreturn [{\n  json: {\n    needs_refresh,\n    access_token: sd.zalo.access_token || null,\n    access_expires_at: sd.zalo.access_expires_at || null,\n    refresh_token: sd.zalo.refresh_token || null,\n    app_id: $input.first().json.app_id\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "25f68c0d-9eef-43be-accc-60bae7d62065",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -848,
        240
      ],
      "parameters": {
        "path": "zalo-intergration-v1",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "e96e8fdf-e031-4afc-b1ab-b6f1b9837158",
      "name": "Load Access Token",
      "type": "n8n-nodes-base.code",
      "position": [
        -592,
        240
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nsd.zalo =    sd.zalo || {};\nreturn [{\n  json: {\n    access_token: sd.zalo.access_token || null,\n    access_expires_at: sd.zalo.access_expires_at || null,\n    refresh_token: sd.zalo.refresh_token || null,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "43e752b8-ec85-4764-addb-71383e69217d",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1856,
        -848
      ],
      "parameters": {
        "width": 944,
        "height": 1712,
        "content": "# Zalo OA Token Auto-Refresh (n8n)\n\n## What this workflow does\n- Maintains a fresh Zalo OA access token using Workflow Static Data (global).\n- Supports both scheduled refresh and a manual \u201creset & re-seed\u201d path.\n- Exposes a lightweight webhook to read the currently cached token for other integrations.\n\n---\n\n## High-level flow\nSchedule Trigger \u2192 Set Refresh Token & App ID \u2192 Load to Static Data \u2192 Refresh Token (Zalo v4) \u2192 Store to SD & Pass token\n\nManual reset path: Execute_Node (Webhook) \u2192 Clean Zalo Static Data \u2192 Set Refresh Token & App ID \u2192 \u2026 (continues flow above)\n\nToken peek path: Webhook (zalo-intergration-v1) \u2192 Load Access Token (returns cached token)\n\n---\n\n## Node-by-node\n\n### 1) Schedule Trigger\nRuns the auto-refresh flow on a fixed interval (every 12 hours) so the access token stays valid.\n\n### 2) Execute_Node (Webhook)\nManual entry point to trigger a reset of cached tokens during testing or when rotating credentials.\n\n### 3) Clean Zalo Static Data\nClears the token cache from Workflow Static Data so the next run re-seeds and refreshes from provided inputs.\n\n### 4) Set Refresh Token and App ID\nProvides the required identifiers and secrets to the flow (refresh token, app ID, secret key).  \nTip: In production, reference environment variables instead of hardcoding.\n\n### 5) Load to Static Data\nInitializes the token cache on first run, checks if a valid access token already exists, and flags whether a refresh is needed (early-refresh buffer applied).  \nNote: With the current wiring, the flow proceeds to refresh on every run.\n\n### 6) Refresh Token (Zalo v4)\nCalls Zalo OAuth to exchange the refresh token for a new access token and returns updated expiry information.\n\n### 7) Store to SD & Pass token\nPersists the latest token and expiry timestamps into Workflow Static Data and passes the current access token forward for immediate downstream use.\n\n### 8) Webhook (zalo-intergration-v1)\nSimple endpoint to request whatever token is currently cached (useful for other services/workflows).\n\n### 9) Load Access Token\nReads the cached access token and its expiry from Workflow Static Data and returns them to the caller of the integration webhook.\n\n---\n\n## Behavior & usage notes\n- The token cache is scoped to this workflow. Other workflows should call this one or use the integration webhook to retrieve the token.\n- An early-refresh buffer reduces the chance of token expiry during active API calls.\n- Secure the manual reset webhook (e.g., IP allowlist or secret) and move sensitive values to environment variables.\n- Optional optimization: insert an IF condition after \u201cLoad to Static Data\u201d to skip the refresh call when the cached token is still valid.\n"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Load Access Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute_Node": {
      "main": [
        [
          {
            "node": "Clean Zalo Static Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Set Refresh Token and App ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load to Static Data": {
      "main": [
        [
          {
            "node": "Refresh Token (Zalo v4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Zalo Static Data": {
      "main": [
        [
          {
            "node": "Set Refresh Token and App ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Refresh Token (Zalo v4)": {
      "main": [
        [
          {
            "node": "Store to SD & Pass token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Refresh Token and App ID": {
      "main": [
        [
          {
            "node": "Load to Static Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}