# Zalo ZNS

ZNS (Zalo Notification Service) gửi một tin nhắn theo mẫu tới số điện thoại qua Zalo Official Account (OA) của bạn. Khách không cần nhắn cho bạn trước, nên ZNS rất hợp để xác nhận đơn hàng, nhắc thanh toán hay báo tình trạng giao hàng. Bạn gửi ZNS được từ trang đơn hàng, tới một danh sách khách hàng đã lọc, từ chu trình, hoặc qua API. Zalo tính phí theo từng tin.

## Trước khi bắt đầu {#before-you-start}

1. **Kết nối Zalo Official Account** ở **Hỗ trợ → Kênh**. Xem [Zalo OA](https://hatang.dev/vi/help/channels/zalo-oa). Dashboard chỉ cho chọn tài khoản đang ở trạng thái **Đang hoạt động**.
2. **Nâng OA lên gói trả phí của Zalo** có ZNS. Nếu chưa, Hạ Tầng không tải được mẫu tin của bạn.
3. **Tạo mẫu và chờ duyệt** trong trang quản trị Zalo OA: oa.zalo.me → ZNS → Mẫu. Hạ Tầng chỉ liệt kê mẫu đã được duyệt.
4. **Giữ module Khách hàng (CRM) đang bật.** ZNS thuộc module Khách hàng: các endpoint API nằm trong module này, và tin đang xếp hàng bị bỏ qua khi module tắt. Xem [Doanh nghiệp và module](https://hatang.dev/vi/help/getting-started/modules).

## Trang Zalo ZNS {#the-zalo-zns-page}

Vào **Hỗ trợ → Kênh**. Thẻ **Zalo ZNS** có nút **Cài đặt ZNS** mở trang Zalo ZNS. Trang này cho OA đang chọn hiển thị:

- **Hạn mức hôm nay**: số tin còn được gửi trong ngày trên tổng hạn mức của OA.
- Tổng số tin đã gửi, thất bại và đang trong hàng đợi.
- Các mẫu đã duyệt với tên, id, trạng thái, chất lượng và giá mỗi tin.
- **Gửi gần đây** với thời gian, số điện thoại, mẫu, trạng thái (kèm lỗi của Zalo khi thất bại) và liên kết tới đơn hàng, khách hàng.

Nếu doanh nghiệp có nhiều OA, chọn OA ở đầu trang.

## Gửi thông báo cho một đơn hàng {#send-a-notification-for-an-order}

1. Mở đơn hàng (**Đơn hàng**, rồi chọn đơn).
2. Trên thẻ **Zalo ZNS**, bấm **Gửi thông báo**.
3. Chọn OA và mẫu tin. Số điện thoại lấy từ địa chỉ giao hàng của đơn; phải là số di động Việt Nam.
4. Điền tham số của mẫu. Hạ Tầng tự điền sẵn các tham số có tên gợi ý tới tên khách, mã đơn, tổng tiền hoặc số điện thoại.
5. Bấm **Gửi ZNS**.

Tin được gửi ngay. Thẻ hiện **Đã gửi (msg …)** với mã tin của Zalo, hoặc lỗi Zalo trả về.

## Gửi cho danh sách khách hàng {#send-to-a-list-of-customers}

1. Vào **Khách hàng** và lọc danh sách (tìm kiếm, giai đoạn, người phụ trách, nhãn, kênh).
2. Bấm **Gửi ZNS** (trang này cũng có trên thanh bên với tên **Gửi ZNS**).
3. Chọn OA, mẫu tin và **Số tin tối đa** (tối đa 5.000).
4. Gán giá trị cho từng **Tham số của mẫu**:

   | Giá trị | Ý nghĩa |
   |---|---|
   | `customer.name`, `customer.phone`, `customer.email` | Thông tin của khách |
   | `field:<key>` | Một trường tuỳ chỉnh, ví dụ `field:member_tier` |
   | `text:` và văn bản cố định phía sau | Cùng một nội dung cho mọi người, ví dụ `text:20/11` |

5. Kiểm tra số lượng: trang cho biết bao nhiêu khách khớp bộ lọc và bao nhiêu khách có số điện thoại. Rồi bấm **Xếp hàng N tin ZNS**.

Khách không có số điện thoại bị bỏ qua. Tin trong hàng đợi được gửi theo tốc độ của OA, kết quả hiện ở mục **Gửi gần đây** trên trang Zalo ZNS. Tin bị Zalo giới hạn tốc độ được thử lại, tối đa 3 lần.

> **Caution**
>
> Zalo tính phí mỗi tin. Kiểm tra số người nhận và giá của mẫu trước khi xếp hàng một đợt gửi lớn.

## Gửi từ chu trình {#send-from-a-workflow}

Thêm bước **Gửi ZNS** vào một [chu trình](https://hatang.dev/vi/docs/guides/support/automation.md#workflows), ví dụ với sự kiện **Đơn đã thanh toán**. Chọn OA, nhập **Mã mẫu ZNS** và tham số, mỗi dòng một cặp `tên=giá trị`. Giá trị dùng được `{{name}}`, `{{phone}}`, `{{email}}`, `{{order_code}}`, `{{order_total}}`, `{{rating}}` và `{{site}}`. Bước này dùng số điện thoại của khách và bị bỏ qua khi khách chưa có số.

## Gửi qua API {#send-with-the-api}

| Endpoint | Dùng để |
|---|---|
| `GET /chat/zns/templates?account_id=cha_…` | Mẫu đã duyệt của một OA, kèm hạn mức hôm nay |
| `GET /chat/zns/templates/{id}?account_id=cha_…` | Một mẫu kèm danh sách tham số |
| `POST /chat/zns` | Gửi ngay một tin theo mẫu |
| `POST /chat/zns/bulk` | Xếp hàng một mẫu cho mọi khách khớp bộ lọc |
| `GET /chat/zns` | Tin doanh nghiệp đã gửi (lọc theo `customer_id`, `order_id`, `limit`) |

Đọc cần scope `crm:read`, gửi cần `crm:write`. Key có `chat:read` / `chat:write` cũng được chấp nhận. `account_id` là id của OA đã kết nối: phần `cha_…` ở cuối địa chỉ trang của OA trong dashboard (`…/chat/channels/zalo/cha_…`).

### Gửi một tin {#send-one-message}

Các trường trong body:

| Trường | Bắt buộc | Ghi chú |
|---|---|---|
| `account_id` | Có | Tài khoản Zalo OA (`cha_…`) |
| `template_id` | Có | Id của mẫu đã duyệt |
| `phone` | Có | Số di động Việt Nam; `0912 345 678`, `84912345678` và `+84 912 345 678` đều được |
| `template_data` | Có | Tham số của mẫu, dạng chuỗi (khoá tối đa 60 ký tự, giá trị tối đa 500) |
| `customer_id`, `order_id` | Không | Gắn tin với khách hàng và đơn hàng |
| `tracking_id` | Không | Mã tham chiếu của bạn, tối đa 60 ký tự; mặc định là id tin của Hạ Tầng |

**cURL**

```bash
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"
  }'
```

**Node.js**

```js
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 từ chối:", data.error);
```

**Python**

```python
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 từ chối:", message["error"])
```

**PHP**

```php
<?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`):

```json
{
  "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"
}
```

> **Note**
>
> Lời gọi trả về `201` cả khi Zalo từ chối tin. Hãy kiểm tra `status`: `sent` nghĩa là Zalo đã nhận (`msg_id` là mã của Zalo); `failed` nghĩa là Zalo từ chối, và `error` chứa mã lỗi, thông báo của Zalo.

### Gửi cho nhiều khách {#send-to-many-customers}

`POST /chat/zns/bulk` nhận `account_id`, `template_id`, `mapping` (gán mỗi tham số của mẫu với `customer.name`, `customer.phone`, `customer.email`, `field:<key>` hoặc `text:<văn bản cố định>`), `filters` không bắt buộc (`q`, `stage`, `owner_id`, `label`, `channel`) và `limit` (1 đến 5.000, mặc định 1.000). Response là `202 Accepted`:

```json
{ "object": "chat_zns_bulk", "queued": 418, "skipped": 12 }
```

`skipped` đếm những khách không có số điện thoại hoặc có số không phải số di động Việt Nam hợp lệ.

### Sự kiện webhook {#webhook-events}

Mỗi lần gửi phát sự kiện `chat.zns.sent` hoặc `chat.zns.failed` kèm đối tượng tin như trên. Xem [Webhook](https://hatang.dev/vi/docs/reference/webhooks.md).

## Giới hạn và lưu ý {#limits-and-quirks}

- Số điện thoại được chuẩn hoá về dạng `84…`. Số nào sau khi chuẩn hoá không có 11 hoặc 12 chữ số bị từ chối với `422 validation_error`.
- Chỉ gửi được mẫu đã duyệt. Mẫu không ở trạng thái bật sẽ không có trong danh sách.
- Hạn mức hằng ngày thuộc về OA và do Zalo đặt. Trang Zalo ZNS và `GET /chat/zns/templates` cho biết hạn mức còn lại.
- `account_id` phải là một Zalo OA. Tài khoản Zalo cá nhân không gửi được ZNS.

## Xử lý sự cố {#troubleshooting}

| Bạn thấy | Nguyên nhân | Cách xử lý |
|---|---|---|
| "Không tải được mẫu: … ZNS cần OA ở gói trả phí và có mẫu đã duyệt." | OA đang ở gói miễn phí, hoặc kết nối đã hết hạn | Nâng gói OA, hoặc kết nối lại ở **Hỗ trợ → Kênh** |
| "Chưa có mẫu nào được duyệt" | Chưa có mẫu nào được Zalo duyệt | Tạo mẫu tại oa.zalo.me → ZNS → Mẫu và chờ duyệt |
| "Hãy kết nối Zalo Official Account … trước" | Chưa có Zalo OA nào đang hoạt động | Kết nối OA, hoặc kết nối lại nếu OA cần cấp quyền lại |
| Tin có trạng thái `failed` | Zalo từ chối: sai tham số, hết hạn mức, hoặc số điện thoại không dùng Zalo | Đọc `error`, sửa dữ liệu mẫu hoặc chờ hạn mức được đặt lại |
| Tin gửi hàng loạt cứ nằm trong hàng đợi | Module Khách hàng đang tắt | Bật lại module |

## Bước tiếp theo {#next-steps}

- [Zalo OA](https://hatang.dev/vi/help/channels/zalo-oa): kết nối Official Account dùng để gửi ZNS.
- [Tự động hoá](https://hatang.dev/vi/docs/guides/support/automation.md): tự gửi ZNS khi đơn được thanh toán.
- [Webhook](https://hatang.dev/vi/docs/reference/webhooks.md): theo dõi `chat.zns.sent` và `chat.zns.failed`.

Source: https://hatang.dev/vi/docs/guides/support/zns
