AutomationFlowsWeb Scraping › Automated Zalo Oa Token Management with Oauth and Webhook Integration

Automated Zalo Oa Token Management with Oauth and Webhook Integration

ByLe Nguyen @leeseifer on n8n.io

This workflow keeps your Zalo Official Account access token valid and easy to reuse across other flows—no external server required.

Cron / scheduled trigger★★★★☆ complexity10 nodesHTTP Request
Web Scraping Trigger: Cron / scheduled Nodes: 10 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #8675 — we link there as the canonical source.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "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
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow keeps your Zalo Official Account access token valid and easy to reuse across other flows—no external server required.

Source: https://n8n.io/workflows/8675/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

As n8n instances scale, teams often lose track of sub-workflows—who uses them, where they are referenced, and whether they can be safely updated. This leads to inefficiencies like unnecessary copies o

HTTP Request, n8n, N8N Trigger +1
Web Scraping

This workflow is an improvement of this workflow by Greg Brzezinka.

HTTP Request, Email Send, XML +1
Web Scraping

N8N-Workflow-Github-Manager. Uses github, httpRequest, n8n. Scheduled trigger; 38 nodes.

GitHub, HTTP Request, n8n
Web Scraping

This workflow uses KlickTipp community nodes, available for self-hosted n8n instances only.

N8N Nodes Klicktipp, Salesforce, Salesforce Trigger +1
Web Scraping

This workflow acts as an automated engagement bot. It sends a Direct Message (DM) with a link or resource to any follower who replies to your post with a specific target keyword.

HTTP Request