> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Zalo

> Send Zalo Notification Service (ZNS) template messages through the Orbit Messaging API

# Zalo

Zalo is Vietnam's leading messaging app. Orbit's Zalo channel wraps the Zalo Notification Service (ZNS) so you can deliver pre-approved template notifications to Vietnamese recipients from the same unified Messaging surface used for every other channel.

<Note>
  Zalo is a **beta** channel. The send and receive paths are wired end to end, but onboarding still requires you to provision a Zalo Official Account and a ZNS access token.
</Note>

## How ZNS works

ZNS is **template-only**: a business sends a pre-approved notification template — a booking confirmation, an OTP, a shipping update — to a customer's phone number. There is no free-form body. `template_name` selects the approved ZNS template id and `template_params` fills the template's named variables. A send without an approved `template_name` is rejected with `VALIDATION_ERROR` (422).

## Send a message

Send a single ZNS notification with `POST /api/v1/messages/zalo` — the direct per-message endpoint, the same shape as every other channel's send route (`/line`, `/telegram`, `/viber`). ZNS is template-only, so the body carries an approved `template_name` and its `template_params` instead of a free-form `body`.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/zalo \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "84901234567",
    "template_name": "booking_confirmation",
    "template_params": { "name": "Linh", "code": "VN-4821" }
  }'
```

### Request body

| Field             | Type   | Required | Description                                                                                 |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------- |
| `to`              | string | yes      | Recipient phone number in E.164 digits (e.g. `84901234567`).                                |
| `template_name`   | string | yes      | The approved ZNS template id. A send without one is rejected with `VALIDATION_ERROR` (422). |
| `template_params` | object | no       | Flat string→string map filling the template's named variables.                              |
| `metadata`        | object | no       | Flat string→string map. Echoed back on delivery and status webhooks.                        |
| `scheduled_at`    | string | no       | RFC 3339 timestamp to defer the send; the message is queued as `scheduled` until then.      |

The sender is selected automatically from the Zalo Official Account credentials connected to your organization — you do not pass an access token on the request.

### Response

`202 Accepted` — the message is queued for delivery; the terminal `delivered` / `failed` state arrives later via the delivery-status webhook.

```json theme={null}
{
  "data": {
    "id": "msg_9f2c1e7a8b2d4c6e0a1f5d9b7c3e2a10",
    "status": "queued",
    "channel": "zalo"
  },
  "meta": {
    "request_id": "req_xyz789",
    "timestamp": "2026-06-08T00:00:00Z"
  }
}
```

## Bulk sends

To deliver an approved ZNS template to many recipients in one call, submit it through the [Campaigns API](/api-reference/endpoints/campaigns) with `channel` set to `zalo`. Each recipient carries the same `to`, `template_name`, and `template_params` fields described above.

## Rate limits

The direct `POST /messages/zalo` endpoint is capped at **80 requests/minute per organization**. Bursts above the cap receive `429` with a `Retry-After` header; back off and retry, or stage high-volume sends through the campaigns API. See [Rate Limits](/guides/rate-limits).

## Configuration

Zalo is a bring-your-own-credential channel. You can connect it per organization in the dashboard, or set a platform-default credential for the whole cluster.

### Per organization (recommended)

1. Create a Zalo Official Account and register your ZNS templates in the [Zalo for Developers](https://developers.zalo.me/) console. Each approved template receives a template id.
2. Generate the OA **access token** (OAuth `oauth/access_token` grant). The token is short-lived; rotate it through Zalo's grant before it expires.
3. In the Orbit dashboard, go to **Settings → Channels → Zalo** and paste the access token. Orbit stores it encrypted at rest (`enc:v1:` envelope) under your organization's `settings.channels.zalo` and never echoes it back through any API response.

Per-organization credentials take precedence; when an organization has none, sends fall through to the platform default below.

### Platform default

An operator can register a single cluster-wide Zalo provider by setting one environment variable. When it is unset the channel stays unregistered and a send fails closed with `CHANNEL_NOT_CONFIGURED`.

| Variable                    | Description                             |
| --------------------------- | --------------------------------------- |
| `DEVOTEL_ZALO_ACCESS_TOKEN` | Zalo Official Account ZNS access token. |

### Credential handling — your responsibility

The ZNS access token is a bearer credential. Anyone holding it can send notifications as your Official Account. Orbit encrypts it at rest and masks it in the dashboard after submit, but its safety upstream of Orbit (your password manager, CI/CD variables, screenshots) is yours to protect:

* **Never commit an access token to git**, public or private.
* **Rotate immediately** through the Zalo grant if you suspect exposure, then re-paste the new value in Orbit.
* **Scope each environment to its own Official Account** so a leaked dev token cannot reach customer conversations.

## Inbound and delivery status

Inbound replies and delivery receipts are normalized by the Orbit messaging gateway and relayed to your account automatically — there is no Zalo webhook for you to register. Replies arrive on the standard `message.received` webhook with `channel: "zalo"`; ZNS delivery callbacks (reconciled by the `tracking_id` Orbit mints per send) advance your outbound message to `delivered` / `failed`. Subscribe to these under **Settings → Webhooks**.

## Common errors

| Code                     | HTTP | Cause                                                                                  | Fix                                                                                         |
| ------------------------ | ---- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `INVALID_RECIPIENT`      | 422  | `to` is missing or empty.                                                              | Supply a recipient phone number.                                                            |
| `VALIDATION_ERROR`       | 422  | The send has no approved `template_name` — ZNS is template-only.                       | Provide an approved ZNS template id.                                                        |
| `CHANNEL_NOT_CONFIGURED` | 503  | No Zalo credentials are connected for the organization and no platform default is set. | Connect an access token under **Settings → Channels → Zalo**.                               |
| `MESSAGE_SEND_FAILED`    | 502  | ZNS rejected the send (expired token, unapproved template, quota).                     | Inspect the ZNS error code in the error message; refresh the token or correct the template. |
| `RATE_LIMITED`           | 429  | More than 80 sends/minute for your organization on `POST /messages/zalo`.              | Honour the `Retry-After` header; stage bulk sends through the campaigns API.                |

## Pricing

Zalo Notification Service has its own per-message pricing set by Zalo. Orbit charges a flat per-1M platform fee for delivery and inbound webhook fan-in. See the [pricing page](https://orbit.devotel.io/pricing).
