Running a Zalo bot on n8n: what breaks between test mode and production

hecigo7 min read
n8nZalo BotWebhookIntegration

You install n8n-nodes-zalo-platform, drop a Zalo Bot Trigger on the canvas, hit Test step, send yourself a message, and it appears. Ten minutes of work.

Then you activate the workflow and messages stop arriving. Nothing is red. No node errors. The execution list is empty.

Every failure below is one we hit for real. The README documents the operations; this covers what happens after they are wired up.

Cloudflare blocks Zalo before n8n ever sees it

This one costs the most hours because there is nothing to debug. n8n logs no request. Zalo reports no error. The webhook is registered correctly on both sides.

Zalo sends webhook callbacks with User-Agent: Java/1.8.0_192. Cloudflare's managed rules treat that as a bot and drop it, and Browser Integrity Check drops it again. Neither leaves a trace in your n8n logs, because the request never reaches your origin.

Two rules fix it, both scoped to the webhook path only:

Rule typeConditionAction
WAF custom ruleURI path contains /webhookSkip all managed rules
Configuration ruleURI path contains /webhookDisable Browser Integrity Check

Use /webhook with no trailing slash. n8n serves production webhooks at /webhook/... and test webhooks at /webhook-test/..., and the shorter prefix covers both. Scope it to the path rather than disabling protection site wide.

If you are not behind Cloudflare, the same class of problem shows up in any WAF, reverse proxy, or bot-filtering layer that inspects User-Agent. Check there first before you touch the node.

The webhook belongs to the workflow, not to you

The trigger node manages the entire webhook lifecycle. On activation it calls POST /setWebhook with the n8n webhook URL, and on deactivation it calls POST /deleteWebhook. You never register it by hand.

It also derives the secret token deterministically:

SHA256(botToken).hex().substring(0, 32)

Every incoming request is validated against the X-Bot-Api-Secret-Token header. Two consequences worth knowing before you debug the wrong thing:

Deriving the token from the bot token means rotating the bot token rotates the secret. That is the behaviour you want, but it also means a stale registration on Zalo's side, left over from a previous token, will fail validation silently rather than loudly.

Test mode and active mode fight over the same registration. If the workflow is active and you click Test step, the test listener spins forever while the production webhook keeps the registration. Deactivate the workflow first, then test. The README lists this as a known symptom, and it is the second most common report we get.

Polling and webhooks are mutually exclusive

The node offers Get Updates, a long-polling operation, for environments with no public HTTPS endpoint: local development, a machine behind NAT, a network where you cannot open inbound ports.

It returns nothing while a webhook is registered. That is Zalo's behaviour, not the node's: a bot delivers through one channel or the other, never both.

So the polling workflow starts with a delete:

Schedule Trigger (every 30s)
  -> Zalo Bot: Delete Webhook      // run once, or the first poll returns empty
  -> Zalo Bot: Get Updates (timeout: 25)
  -> IF (has data) -> process

Set the long-poll timeout below the schedule interval. At 25 seconds against a 30 second schedule, each poll closes before the next one opens. Invert those numbers and you get overlapping requests against the same bot.

Polling is a development convenience. In production it costs an API round trip every interval whether or not anyone messaged you, and it adds up to half the interval in latency. Use webhooks once you have HTTPS.

Queue mode needs the node on every container

If you run n8n in queue mode, the main process registers webhooks and the workers execute. Community nodes install per container.

Install the node only on main and the trigger registers fine, the webhook arrives fine, and then execution fails on a worker that has never heard of n8n-nodes-zalo-platform. The failure looks like a node problem. It is a deployment problem.

Install on every container that runs workflows, and redeploy them together. The same applies to any community node, but this one is easy to miss because the registration half works.

The limits that bite later

Four constraints that do not matter in a demo and do matter at volume:

Message length is capped at 2000 characters. Anything assembled from a template, an AI response, or a database field needs a length check before Send Message. Truncate deliberately, or split into several messages, but decide rather than discover.

Webhook delivery is at-least-once. If your endpoint is slow to answer, Zalo retries, and you process the same message twice. Anything with a side effect (creating a ticket, charging something, sending a reply) needs a deduplication key. message_id from the payload is the natural one.

Chat IDs are the only handle you get. The payload carries message.chat.id and message.from.display_name, no phone number and no email. If you need to join a Zalo conversation to a customer record, you have to capture that mapping the first time a person messages you and store it. There is no lookup after the fact.

A group chat and a private chat look similar but are not. message.chat.chat_type is PRIVATE or a group type, and replies behave differently. Branch on it early rather than discovering it when a bot replies to a group with something meant for one person.

Where this leaves you

The node is thin on purpose: it is a typed wrapper over POST https://bot-api.zaloplatforms.com/bot{TOKEN}/{method} with the webhook lifecycle handled for you. Everything above is about the layer around it, which is where integration work actually lives.

n8n-nodes-zalo-platform is MIT licensed and published on npm at version 1.0.16. If you hit a case it does not handle, open an issue on GitHub with the payload; that is the fastest route to it being covered.

📖

Tối Ưu Tự Động Hóa Zalo Bot với n8n: Hướng Dẫn Chi Tiết từ hecigo

Zalo là ứng dụng nhắn tin phổ biến nhất tại Việt Nam, với hơn 75 triệu người dùng. Nếu doanh nghiệp của bạn hoạt động tại Việt Nam, khách hàng của...

Where the harder problems are

Delivering a message is the easy half. The hard half starts when the conversation has to reach a system of record: a CRM that must not create the same customer twice, an order system whose state changes after the message was sent, a reconciliation job that notices when a message was accepted but never stored.

That is the layer we build and run.

🚀

Found this useful? Follow hecigo on Zalo OA for new technical writing, or get in touch if two of your systems need to talk to each other and something is going wrong in between.

📖

Read next: Middleware: phần việc n8n, OpenClaw và mọi nền tảng tự động hóa không làm hộ bạn

Nối được API là phần dễ. Phần khó lộ ra sau vài tuần chạy thật: sự kiện gửi lại hai lần, webhook rơi mất một giao dịch, hóa đơn bị hủy nhưng hệ...

References

Related Articles