Zalo ZNS
ZNS (Zalo Notification Service) sends a template message to a phone number through your Zalo Official Account (OA). The customer does not need to have messaged you first, which makes ZNS a good fit for order confirmations, payment reminders and delivery updates. You can send ZNS from an order page, to a filtered list of customers, from a workflow, or through the API. Zalo charges for each message.
Before you start
- Connect a Zalo Official Account under Support → Channels. See Zalo OA. The dashboard offers only accounts whose status is Active.
- Put the OA on a paid Zalo tier that includes ZNS. Without it, Ha Tang cannot load your templates.
- Create templates and get them approved in the Zalo OA admin: oa.zalo.me → ZNS → Templates. Ha Tang lists only approved templates.
- Keep the CRM (Customers) module on. ZNS belongs to the Customers module: the API endpoints sit in that module, and queued sends are skipped while it is off. See Businesses and modules.
The Zalo ZNS page
Open Support → Channels. The Zalo ZNS card has a ZNS settings button that opens the Zalo ZNS page. It shows, for the selected OA:
- Quota today: messages left today out of the OA's daily quota.
- Totals: sent, failed and queued messages.
- Approved templates with their name, id, status, quality and price per message.
- Recent sends with the time, phone number, template, status (and Zalo's error when it failed), and links to the order and customer.
If the business has several OAs, pick one at the top of the page.
Send a notification for an order
- Open the order (Orders, then the order).
- On the Zalo ZNS card, click Send notification.
- Pick the OA and the template. The phone number comes from the order's shipping address; it must be a Vietnamese mobile number.
- Fill in the template parameters. Ha Tang pre-fills parameters whose names suggest the customer's name, the order code, the total or the phone number.
- Click Send ZNS.
The message is sent right away. The card shows Sent (msg …) with Zalo's message id, or Zalo's error.
Send to a list of customers
-
Open Customers and filter the list (search, stage, owner, label, channel).
-
Click Send ZNS (the same page is in the sidebar as ZNS sending).
-
Choose the OA and the template, and set Max messages (up to 5,000).
-
Map each Template parameter to a value:
Value Meaning customer.name,customer.phone,customer.emailThe customer's details field:<key>A custom field, for example field:member_tiertext:followed by fixed textThe same text for everyone, for example text:20/11 -
Check the count: the page shows how many customers match and how many have a phone number. Then click Queue N ZNS messages.
Customers without a phone number are skipped. Queued messages go out at the OA's pace, and the results appear under Recent sends on the Zalo ZNS page. Messages that hit Zalo's rate limit are retried, up to 3 attempts.
Zalo charges for every message. Check the number of recipients and the template price before you queue a large send.
Send from a workflow
Add a Send ZNS step to a workflow, for example on An order is paid. Pick the OA, enter the ZNS template id and the parameters, one name=value per line. Values can use {{name}}, {{phone}}, {{email}}, {{order_code}}, {{order_total}}, {{rating}} and {{site}}. The step uses the customer's phone number and is skipped when there is none.
Send with the API
| Endpoint | Purpose |
|---|---|
GET /chat/zns/templates?account_id=cha_… | Approved templates of an OA, with today's quota |
GET /chat/zns/templates/{id}?account_id=cha_… | One template with its parameters |
POST /chat/zns | Send one template message now |
POST /chat/zns/bulk | Queue a template for every customer matching filters |
GET /chat/zns | Messages sent by the business (customer_id, order_id, limit filters) |
Reads need the crm:read scope and sends need crm:write. Keys with chat:read / chat:write are accepted too. account_id is the id of the connected OA: the cha_… at the end of the OA's page address in the dashboard (…/chat/channels/zalo/cha_…).
Send one message
Body fields:
| Field | Required | Notes |
|---|---|---|
account_id | Yes | The Zalo OA account (cha_…) |
template_id | Yes | An approved template id |
phone | Yes | A Vietnamese mobile number; 0912 345 678, 84912345678 and +84 912 345 678 all work |
template_data | Yes | Template parameters, as strings (keys up to 60 characters, values up to 500) |
customer_id, order_id | No | Link the message to a customer and an order |
tracking_id | No | Your own reference, up to 60 characters; defaults to the Ha Tang message id |
- cURL
- Node.js
- Python
- PHP
curl -X POST https://api.hatang.dev/chat/zns \
-H "Authorization: Bearer ht_123..." \
-H "Content-Type: application/json" \
-d '{
"account_id": "cha_123",
"template_id": "231234",
"phone": "0912345678",
"template_data": { "customer_name": "Lan", "order_code": "DH1024", "amount": "350.000đ" },
"order_id": "ord_123"
}'
import { Hatang } from "@hatang/sdk";
const hatang = new Hatang(process.env.HATANG_API_KEY);
const { data, error } = await hatang.chat.zns.send({
account_id: "cha_123",
template_id: "231234",
phone: "0912345678",
template_data: { customer_name: "Lan", order_code: "DH1024", amount: "350.000đ" },
order_id: "ord_123",
});
if (error) throw new Error(error.message);
if (data.status === "failed") console.error("Zalo refused:", data.error);
import os, requests
res = requests.post(
"https://api.hatang.dev/chat/zns",
headers={"Authorization": f"Bearer {os.environ['HATANG_API_KEY']}"},
json={
"account_id": "cha_123",
"template_id": "231234",
"phone": "0912345678",
"template_data": {"customer_name": "Lan", "order_code": "DH1024", "amount": "350.000đ"},
"order_id": "ord_123",
},
)
message = res.json()
if message.get("status") == "failed":
print("Zalo refused:", message["error"])
<?php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.hatang.dev']);
$res = $client->post('/chat/zns', [
'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
'json' => [
'account_id' => 'cha_123',
'template_id' => '231234',
'phone' => '0912345678',
'template_data' => ['customer_name' => 'Lan', 'order_code' => 'DH1024', 'amount' => '350.000đ'],
'order_id' => 'ord_123',
],
]);
$message = json_decode($res->getBody(), true);
Response (201 Created):
{
"object": "chat_zns_message",
"id": "zns_...",
"account_id": "cha_123",
"customer_id": null,
"order_id": "ord_123",
"phone": "84912345678",
"template_id": "231234",
"template_name": null,
"template_data": { "customer_name": "Lan", "order_code": "DH1024", "amount": "350.000đ" },
"tracking_id": null,
"msg_id": "f1d2...",
"status": "sent",
"error": null,
"created_at": "2026-10-23T03:00:00.000Z"
}
The call returns 201 even when Zalo refuses the message. Check status: sent means Zalo accepted it (msg_id is Zalo's id); failed means Zalo refused it, and error holds Zalo's error code and message.
Send to many customers
POST /chat/zns/bulk takes account_id, template_id, mapping (each template parameter mapped to customer.name, customer.phone, customer.email, field:<key> or text:<fixed text>), optional filters (q, stage, owner_id, label, channel) and limit (1 to 5,000, default 1,000). It responds 202 Accepted:
{ "object": "chat_zns_bulk", "queued": 418, "skipped": 12 }
skipped counts customers without a phone number or with a number that is not a valid Vietnamese mobile number.
Webhook events
Each delivery attempt sends chat.zns.sent or chat.zns.failed with the message object above. See Webhooks.
Limits and quirks
- Phone numbers are normalized to the
84…form. A number that does not end up with 11 or 12 digits is rejected with422 validation_error. - Only approved templates can be sent. Templates with a status other than enabled do not appear in the list.
- The daily quota belongs to the OA and is set by Zalo. The Zalo ZNS page and
GET /chat/zns/templatesshow the remaining quota. account_idmust be a Zalo OA. A Zalo personal account cannot send ZNS.
Troubleshooting
| What you see | Cause | Fix |
|---|---|---|
| "Could not load templates: … ZNS needs the OA on a paid tier with approved templates." | The OA is on a free tier, or its connection expired | Upgrade the OA, or reconnect it under Support → Channels |
| "No approved templates" | No template has been approved yet | Create templates at oa.zalo.me → ZNS → Templates and wait for approval |
| "Connect a Zalo Official Account first" | No active Zalo OA | Connect the OA, or reconnect it if it needs re-authorization |
A send has status failed | Zalo refused it: wrong parameters, quota exhausted, or the phone number has no Zalo account | Read the error, fix the template data or wait for the quota to reset |
| Bulk messages stay queued | The Customers module is off | Turn the module back on |
Next steps
- Zalo OA: connect the Official Account that sends ZNS.
- Automation: send ZNS automatically when an order is paid.
- Webhooks reference: track
chat.zns.sentandchat.zns.failed.