# Sự kiện

Sự kiện là thứ **khởi động** một luồng: khối Kích hoạt chọn một loại sự kiện, và mỗi lần sự kiện ấy xảy ra ở shop, luồng chạy với một mục là **envelope** của sự kiện. DANIX chỉ ghi sự kiện khi shop có luồng đang bật nghe loại ấy (và với tin nhắn, nghe đúng trang ấy) và shop còn dùng được tính năng Tự động hoá.

## Envelope

```json
{
  "id": "evt_018f3b8e-…",
  "type": "order.status_changed",
  "apiVersion": "v1",
  "createdAt": "2026-10-01T08:30:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "changes": { "fields": ["status"], "status": { "from": "new", "to": "confirmed" } },
  "data": { "id": "…", "code": "…", "status": "confirmed" }
}
```

| Trường | Ý nghĩa |
| --- | --- |
| `id` | Mã của **sự kiện**: chuỗi có tiền tố `evt_`, tối đa 68 ký tự. Cùng một giá trị ở mọi luồng nhận cùng sự kiện. Khác với `webhook-id` (mã của lượt gửi). Coi nó là chuỗi mờ đục — **không phải UUID**, đừng lưu vào cột kiểu `uuid`. |
| `type` | Loại sự kiện, xem bảng bên dưới. |
| `apiVersion` | Phiên bản hình dạng dữ liệu, hiện là `v1`. |
| `createdAt` | Thời điểm sự kiện xảy ra, ISO 8601 UTC. |
| `shop` | Shop phát sinh sự kiện: `id` và `slug`. |
| `changes` | Chỉ có ở các sự kiện sửa. Xem bên dưới. |
| `data` | Nội dung của tài nguyên, **cùng hình dạng với phản hồi `GET` tương ứng** trong [tham chiếu](/developers/reference/orders): bạn đọc `data` y như đã đọc một bản ghi qua API. |

### `changes`

`changes` cho biết **trường nào đã đổi**, không chứa giá trị cũ của chúng:

- `fields`: danh sách tên trường đã đổi — đúng tên trường của `data` (camelCase, ví dụ `shippingFee`; trường của mẫu mã viết `variants.price`). Đổi liên hệ hay địa chỉ của khách hiện là `contacts` / `addresses`, đổi ảnh sản phẩm là `images`, mốc mới của chặng hoàn (vận đơn giao một phần) là `events`. Thay đổi ở phần dữ liệu không có trong `data` không được liệt kê, và lượt sửa chỉ chạm phần ấy (ví dụ đường dẫn rút gọn của sản phẩm, ảnh đại diện của khách, ghi chú in của đơn) **không được gửi**: DANIX không gửi gì cho bạn, luồng dừng ngay ở khối Kích hoạt (nhật ký của shop vẫn ghi một lượt chạy bị bỏ qua, ẩn mặc định);
- `status`: có khi trạng thái đổi, dạng `{ "from": …, "to": … }`. Đây là giá trị duy nhất ngoài tên trường mà `changes` mang.

Muốn biết giá trị mới của một trường, đọc `data`. Muốn biết giá trị cũ, bạn tự giữ bản sao ở phía mình.

## Danh mục sự kiện

| `type` | Khi nào | `data` là | Quyền đọc cần có |
| --- | --- | --- | --- |
| `order.created` | Đơn hàng được tạo | Đơn hàng | `pos.orders.read` |
| `order.updated` | Một trường trong `data` của đơn được sửa | Đơn hàng | `pos.orders.read` |
| `order.status_changed` | Đơn đổi trạng thái | Đơn hàng | `pos.orders.read` |
| `order.deleted` | Đơn bị xoá | Đơn hàng | `pos.orders.read` |
| `product.created` | Sản phẩm được tạo | Sản phẩm | `pos.products.read` |
| `product.updated` | Một trường trong `data` của sản phẩm được sửa (kể cả mẫu mã, ảnh) | Sản phẩm | `pos.products.read` |
| `product.deleted` | Sản phẩm bị xoá | Sản phẩm | `pos.products.read` |
| `inventory.stock_changed` | Tồn kho của một mẫu mã đổi | Dòng tồn kho | `pos.inventory.read` và `pos.products.read` |
| `customer.created` | Khách hàng được tạo | Khách hàng | `pos.customers.read` |
| `customer.updated` | Một trường trong `data` của khách được sửa (kể cả liên hệ, địa chỉ) | Khách hàng | `pos.customers.read` |
| `customer.deleted` | Khách hàng bị xoá | Khách hàng | `pos.customers.read` |
| `shipment.created` | Vận đơn được tạo | Vận đơn | `pos.shipping.read` và `pos.orders.read` |
| `shipment.status_changed` | Vận đơn đổi `status`, hoặc chặng hoàn của đơn giao một phần sang bước mới | Vận đơn | `pos.shipping.read` và `pos.orders.read` |
| `message.received` | Khách nhắn tin tới một trang chat | Tin nhắn | `social.pages.read` và `social.conversations.read` |
| `message.sent` | Shop gửi tin tới khách | Tin nhắn | `social.pages.read` và `social.conversations.read` |

Luồng chạy dưới một tài khoản riêng của chính nó; **chỉ gửi đi dữ liệu mà tài khoản ấy đọc được** theo cột "Quyền đọc cần có", và với tin nhắn chỉ trên các trang đã chọn cho luồng. Người chỉ có quyền xem luồng không đọc được nội dung gửi của loại dữ liệu mà họ không có quyền đọc.

## Ví dụ từng loại

Các ví dụ dưới đây rút gọn `data` thành `id` kèm vài trường; `data` thật đầy đủ như phản hồi `GET` tương ứng. Nút **Chạy thử** trong ứng dụng Tự động hoá gửi envelope mẫu đầy đủ của từng loại tới khối của bạn.

### Đơn hàng

```json
{ "id": "evt_018f3b8e-…", "type": "order.created", "apiVersion": "v1", "createdAt": "2026-10-01T08:30:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "data": { "id": "…", "code": "DH0001", "status": "new" } }
```

```json
{ "id": "evt_018f3b8e-…", "type": "order.updated", "apiVersion": "v1", "createdAt": "2026-10-01T08:35:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "changes": { "fields": ["note", "shippingFee"] },
  "data": { "id": "…", "code": "DH0001", "status": "new" } }
```

```json
{ "id": "evt_018f3b8e-…", "type": "order.status_changed", "apiVersion": "v1", "createdAt": "2026-10-01T09:00:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "changes": { "fields": ["status"], "status": { "from": "new", "to": "confirmed" } },
  "data": { "id": "…", "code": "DH0001", "status": "confirmed" } }
```

`order.deleted` có cùng hình dạng, `data` là bản ghi đơn đã bị xoá: `status` là `deleted` và có `deletedAt`. `product.deleted` và `customer.deleted` cũng mang `deletedAt`.

### Sản phẩm, khách hàng

`product.created`, `product.updated`, `product.deleted` và `customer.created`, `customer.updated`, `customer.deleted` cùng dạng envelope với đơn hàng: `data` là sản phẩm hay khách hàng; các sự kiện `updated` mang `changes.fields`.

```json
{ "id": "evt_018f3b8e-…", "type": "customer.updated", "apiVersion": "v1", "createdAt": "2026-10-01T10:00:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "changes": { "fields": ["contacts"] },
  "data": { "id": "…" } }
```

### Tồn kho

```json
{ "id": "evt_018f3b8e-…", "type": "inventory.stock_changed", "apiVersion": "v1", "createdAt": "2026-10-01T11:00:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "changes": { "fields": ["quantity"] },
  "data": { "…": "dòng tồn kho của một mẫu mã tại một kho" } }
```

### Vận đơn

```json
{ "id": "evt_018f3b8e-…", "type": "shipment.status_changed", "apiVersion": "v1", "createdAt": "2026-10-01T12:00:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "changes": { "fields": ["status"], "status": { "from": "…", "to": "…" } },
  "data": { "id": "…" } }
```

`shipment.created` cùng dạng, không có `changes`.

Đơn giao một phần: chặng đi kết thúc ở `partially_returned` (trạng thái cuối, không đổi nữa), rồi phần hàng hoàn về theo một mã riêng của hãng trên **cùng** vận đơn. Mỗi bước của chặng hoàn phát một `shipment.status_changed` không có `changes.status` (vì `status` đứng yên), `changes.fields` là `["events"]`, và mốc mới nằm trong `data.events`:

```json
{ "id": "evt_018f3b8e-…", "type": "shipment.status_changed", "apiVersion": "v1", "createdAt": "2026-10-03T09:00:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "changes": { "fields": ["events"] },
  "data": { "id": "…", "status": "partially_returned", "events": ["…"] } }
```

### Tin nhắn

```json
{ "id": "evt_018f3b8e-…", "type": "message.received", "apiVersion": "v1", "createdAt": "2026-10-01T13:00:00.000Z",
  "shop": { "id": "…", "slug": "ten-shop" },
  "data": { "id": "…" } }
```

`message.sent` cùng dạng. Khi khối Kích hoạt chọn một trong hai sự kiện tin nhắn, bạn phải chọn **trang** chat mà luồng lắng nghe.

## Ghi chú cho bên nhận

- Danh mục sự kiện và trường trong `data` có thể được **bổ sung** mà không đổi phiên bản: bỏ qua loại sự kiện và trường lạ.
- Sự kiện giao theo kiểu ít nhất một lần và không đảm bảo thứ tự: khử trùng theo `webhook-id` (hoặc theo `id` của sự kiện nếu bạn nhận ở nhiều luồng), và dùng `createdAt` của **envelope** để biết sự kiện nào mới hơn — không phải mọi `data` đều có `updatedAt` (sản phẩm, chi tiết vận đơn và dòng tồn không có). Xem [Luồng tự động và webhook](/developers/webhooks).
- Tạo mới chỉ phát `*.created`: tạo đơn không kèm `order.status_changed`, và liên hệ, địa chỉ, mẫu mã, ảnh tạo cùng lúc với khách hay sản phẩm không kèm `*.updated`.
- Đơn "trả để đổi" (`data.exchangeReturn` là `true`) là đơn khách gửi hàng về để ĐỔI, không phải để hoàn tiền. Lượt duyệt hoàn đổi mục đích của đơn (hoàn ↔ đổi) gửi `order.updated` với `changes.fields` chứa `exchangeReturn`, kể cả khi trạng thái và tiền không đổi.
- `*.updated` chỉ được gửi khi có ít nhất một trường của `data` đổi, nên luôn mang `changes`; sửa phần dữ liệu không có trong `data` không gửi gì. `shipment.status_changed` được gửi khi `status` đổi (mang `changes.status`) và khi chặng hoàn của đơn giao một phần sang bước mới (`changes.fields` là `["events"]`, không có `changes.status`). Mốc hành trình khác không đổi `status` không phát sự kiện; đọc chúng ở `data.events` khi gọi `GET`.
- Webhook là đường thời gian thực; [`updatedSince`](/developers/pagination) là đường đối soát bù những gì bạn có thể đã lỡ.
