Bỏ qua tới nội dung

Tài liệu API v1 · sẽ ngừng

API v1 (cũ)

v1 vẫn chạy ít nhất 6 tháng để phần mềm cũ không bị gián đoạn, nhưng không thêm thao tác mới. Mọi phản hồi v1 có header Deprecation và liên kết sang v2. Dùng API v2 và MCP cho tích hợp mới — đủ mọi thao tác của ứng dụng.

Xác thực & quyền

Chủ hoặc quản trị workspace tạo khoá trong Cài đặt → Kết nối AI & API. Khoá chỉ hiện một lần; WA Auto chỉ lưu bản băm. Gửi khoá trong header:

curl /api/public/v1/contacts \
  -H "Authorization: Bearer waa_live_…"

Mỗi khoá chỉ làm được những gì đã chọn khi tạo:

QuyềnCho phép
contacts.readĐọc khách hàng — Xem danh sách và hồ sơ khách
contacts.writeGhi khách hàng — Tạo, sửa khách, gắn/bỏ nhãn
conversations.readĐọc hội thoại — Xem hội thoại và tin nhắn
messages.sendGửi tin — Gửi tin qua cổng giao việc, chịu cùng hạn mức
campaignsChiến dịch — Xem chiến dịch, thêm khách vào chiến dịch

API có ở mọi gói. Khoá bị thu hồi hoặc gói hết hạn thì mọi yêu cầu bị từ chối ngay. Khoá tạo trước Build 1.3 là “khoá cũ”: giữ đúng các quyền trên.

Giới hạn & phân trang

  • Tối đa 60 yêu cầu mỗi phút cho một khoá; quá thì trả 429 kèm Retry-After. Mọi phản hồi có RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset.
  • Mọi lệnh POST cần header Idempotency-Key (1–255 ký tự, mỗi lệnh một giá trị mới). Gửi lại cùng khoá + cùng nội dung trong 24 giờ nhận đúng kết quả cũ kèm Idempotent-Replayed: true — không có việc đôi. Cùng khoá mà nội dung khác → 422; yêu cầu đầu còn chạy → 409. Lỗi máy chủ (5xx) không được nhớ, gửi lại sẽ chạy thật.
  • Thân yêu cầu tối đa 64 KiB, tính theo byte.
  • Danh sách trả { data, nextCursor }. Gửi lại cursor=<nextCursor> để lấy trang sau; limit tối đa 100.
  • Thời gian theo chuẩn ISO 8601 (UTC). Số điện thoại trả về dạng +84…

Mã lỗi

Lỗi luôn có dạng { "error": { "code": "…", "message": "…" } }.

HTTPcodeÝ nghĩa
400invalid_jsonNội dung không phải JSON.
400idempotency_key_required / invalid_idempotency_keyLệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.
401missing_key / invalid_keyThiếu khoá, sai khoá hoặc khoá đã thu hồi.
403missing_scopeKhoá không có quyền cho thao tác này.
403plan_no_api / plan_expiredGói chưa có API hoặc đã hết hạn.
404not_foundKhông có đối tượng này trong workspace của khoá.
409duplicate_phone / do_not_contact / campaign_closedTrùng số, khách đã xin dừng nhận tin, hoặc chiến dịch đã kết thúc.
409request_in_progressYêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.
413body_too_largeNội dung quá 64 KiB (tính theo byte).
422invalid_inputDữ liệu chưa hợp lệ; xem details.
422idempotency_key_reusedIdempotency-Key đã dùng cho một yêu cầu có nội dung khác.
429rate_limitedQuá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.
500server_errorLỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).
503rate_limit_unavailable / idempotency_unavailableTạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.

Khách hàng

GET/api/public/v1/contactsquyền contacts.read

Danh sách khách

Mới nhất trước. Lọc theo từ khoá, nhãn hoặc số điện thoại.

Tham sốỞ đâuMô tả
qqueryTìm theo tên, số, email, công ty.
tagqueryTên nhãn.
phonequerySố điện thoại (0901…, +84901…).
limitquerySố dòng mỗi trang, 1–100 (mặc định 50).
cursorqueryGiá trị nextCursor của trang trước.
curl -X GET "/api/public/v1/contacts" \
  -H "Authorization: Bearer waa_live_…"

Trả về 200

{
  "data": [
    {
      "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
      "phone": "+84901234567",
      "name": "Chị Lan",
      "whatsappName": "Lan Spa Hoa",
      "email": "lan@example.com",
      "company": "Spa Hoa",
      "notes": null,
      "stageId": null,
      "dealValue": 2500000,
      "doNotContact": false,
      "tags": [
        "VIP"
      ],
      "createdAt": "2026-10-17T03:15:00.000Z",
      "lastInboundAt": "2026-10-17T07:28:00.000Z",
      "lastOutboundAt": "2026-10-17T07:29:00.000Z"
    }
  ],
  "nextCursor": "MjAyNi0xMC0xN1QwMzoxNTowMC4wMDBafDdiMDI0ZTA2…"
}
POST/api/public/v1/contactsquyền contacts.write

Tạo khách

Số được chuẩn hoá theo quốc gia mặc định của workspace. Trùng số trả 409 kèm contactId có sẵn.

curl -X POST "/api/public/v1/contacts" \
  -H "Authorization: Bearer waa_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"phone":"0901234567","name":"Chị Lan","email":"lan@example.com","company":"Spa Hoa","tags":["Website"]}'

Trả về 201

{
  "data": {
    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
    "phone": "+84901234567",
    "name": "Chị Lan",
    "whatsappName": "Lan Spa Hoa",
    "email": "lan@example.com",
    "company": "Spa Hoa",
    "notes": null,
    "stageId": null,
    "dealValue": 2500000,
    "doNotContact": false,
    "tags": [
      "VIP"
    ],
    "createdAt": "2026-10-17T03:15:00.000Z",
    "lastInboundAt": "2026-10-17T07:28:00.000Z",
    "lastOutboundAt": "2026-10-17T07:29:00.000Z"
  }
}
GET/api/public/v1/contacts/{contactId}quyền contacts.read

Xem một khách

Tham sốỞ đâuMô tả
contactIdđường dẫnMã khách.
curl -X GET "/api/public/v1/contacts/{contactId}" \
  -H "Authorization: Bearer waa_live_…"

Trả về 200

{
  "data": {
    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
    "phone": "+84901234567",
    "name": "Chị Lan",
    "whatsappName": "Lan Spa Hoa",
    "email": "lan@example.com",
    "company": "Spa Hoa",
    "notes": null,
    "stageId": null,
    "dealValue": 2500000,
    "doNotContact": false,
    "tags": [
      "VIP"
    ],
    "createdAt": "2026-10-17T03:15:00.000Z",
    "lastInboundAt": "2026-10-17T07:28:00.000Z",
    "lastOutboundAt": "2026-10-17T07:29:00.000Z"
  }
}
PATCH/api/public/v1/contacts/{contactId}quyền contacts.write

Sửa khách

Chỉ gửi trường cần đổi. Không đổi được số điện thoại qua API.

Tham sốỞ đâuMô tả
contactIdđường dẫnMã khách.
curl -X PATCH "/api/public/v1/contacts/{contactId}" \
  -H "Authorization: Bearer waa_live_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Chị Lan (Spa Hoa Q3)","dealValue":3000000}'

Trả về 200

{
  "data": {
    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
    "phone": "+84901234567",
    "name": "Chị Lan",
    "whatsappName": "Lan Spa Hoa",
    "email": "lan@example.com",
    "company": "Spa Hoa",
    "notes": null,
    "stageId": null,
    "dealValue": 2500000,
    "doNotContact": false,
    "tags": [
      "VIP"
    ],
    "createdAt": "2026-10-17T03:15:00.000Z",
    "lastInboundAt": "2026-10-17T07:28:00.000Z",
    "lastOutboundAt": "2026-10-17T07:29:00.000Z"
  }
}
POST/api/public/v1/contacts/{contactId}/tagsquyền contacts.write

Gắn / bỏ nhãn

Nhãn chưa có sẽ được tạo. Gắn nhãn kích hoạt luật tự động “Khách được gắn nhãn”.

Tham sốỞ đâuMô tả
contactIdđường dẫnMã khách.
curl -X POST "/api/public/v1/contacts/{contactId}/tags" \
  -H "Authorization: Bearer waa_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"add":["VIP"],"remove":["Mới"]}'

Trả về 200

{
  "data": {
    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
    "tags": [
      "VIP"
    ]
  }
}

Hội thoại

GET/api/public/v1/conversationsquyền conversations.read

Danh sách hội thoại

Mới có tin trước.

Tham sốỞ đâuMô tả
accountIdqueryChỉ hội thoại của một số WhatsApp.
statusqueryopen | pending | closed
unansweredquerytrue: chỉ hội thoại khách đang chờ trả lời.
limitquerySố dòng mỗi trang, 1–100 (mặc định 50).
cursorqueryGiá trị nextCursor của trang trước.
curl -X GET "/api/public/v1/conversations" \
  -H "Authorization: Bearer waa_live_…"

Trả về 200

{
  "data": [
    {
      "id": "1cc08299-a808-4936-a102-c4045341130f",
      "accountId": "a3e6f771-6ab9-4614-9165-7be3d240ffcf",
      "type": "direct",
      "contactId": "7b024e06-36ae-46d0-91d1-2185cd71837f",
      "title": "Chị Lan",
      "status": "open",
      "assigneeMemberId": null,
      "unread": 2,
      "waitingSince": "2026-10-17T07:28:00.000Z",
      "archived": false,
      "lastMessageAt": "2026-10-17T07:28:00.000Z",
      "lastMessagePreview": "Cho mình hỏi giá combo"
    }
  ],
  "nextCursor": null
}
GET/api/public/v1/conversations/{conversationId}quyền conversations.read

Xem một hội thoại

Tham sốỞ đâuMô tả
conversationIdđường dẫnMã hội thoại.
curl -X GET "/api/public/v1/conversations/{conversationId}" \
  -H "Authorization: Bearer waa_live_…"

Trả về 200

{
  "data": {
    "id": "1cc08299-a808-4936-a102-c4045341130f",
    "type": "direct",
    "status": "open"
  }
}
GET/api/public/v1/conversations/{conversationId}/messagesquyền conversations.read

Tin nhắn trong hội thoại

Mới nhất trước. Tệp đính kèm chỉ có tên và loại tệp.

Tham sốỞ đâuMô tả
conversationIdđường dẫnMã hội thoại.
limitquerySố dòng mỗi trang, 1–100 (mặc định 50).
cursorqueryGiá trị nextCursor của trang trước.
curl -X GET "/api/public/v1/conversations/{conversationId}/messages" \
  -H "Authorization: Bearer waa_live_…"

Trả về 200

{
  "data": [
    {
      "id": "4f0c…",
      "direction": "in",
      "kind": "text",
      "text": "Cho mình hỏi giá combo",
      "status": "sent",
      "at": "2026-10-17T07:28:00.000Z",
      "sentBy": "contact",
      "contactId": "7b024e06-36ae-46d0-91d1-2185cd71837f",
      "campaignId": null,
      "attachments": []
    }
  ],
  "nextCursor": null
}

Gửi tin

POST/api/public/v1/messagesquyền messages.send

Gửi tin

Tin được xếp vào cổng giao việc như mọi tin khác: luôn chịu danh sách loại trừ (STOP), và chịu hạn mức, làm nóng, giờ gửi nếu chủ số đã bật. Gửi cho người đã nhắn tới số này tính là tin tự động; gửi cho người mới đi làn chiến dịch (chậm hơn, có thể bị hoãn). Trả 202 khi đã xếp hàng — theo dõi kết quả bằng webhook message.status. Gửi lại cùng Idempotency-Key không gửi trùng (trường idempotencyKey trong thân vẫn nhận như tên cũ của tiêu đề, sẽ bỏ).

curl -X POST "/api/public/v1/messages" \
  -H "Authorization: Bearer waa_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"accountId":"a3e6f771-6ab9-4614-9165-7be3d240ffcf","phone":"0901234567","text":"Chào chị Lan, lịch hẹn của chị là 10h sáng thứ 7 ạ."}'

Trả về 202

{
  "data": {
    "messageId": "9d2e…",
    "jobId": "c51a…",
    "conversationId": "1cc08299-a808-4936-a102-c4045341130f",
    "status": "sending",
    "duplicate": false,
    "lane": "reply"
  }
}

Chiến dịch

GET/api/public/v1/campaignsquyền campaigns

Danh sách chiến dịch

Tham sốỞ đâuMô tả
limitquerySố dòng mỗi trang, 1–100 (mặc định 50).
curl -X GET "/api/public/v1/campaigns" \
  -H "Authorization: Bearer waa_live_…"

Trả về 200

{
  "data": [
    {
      "id": "e1b0…",
      "name": "Chăm sóc khách tháng 10",
      "kind": "sequence",
      "status": "running",
      "targets": 120,
      "startedAt": "2026-10-15T02:00:00.000Z",
      "finishedAt": null,
      "createdAt": "2026-10-14T09:00:00.000Z"
    }
  ]
}
POST/api/public/v1/campaigns/{campaignId}/enrollquyền campaigns

Thêm khách vào chiến dịch

Bản nháp: khách được thêm vào danh sách, lọc an toàn khi bắt đầu. Đã hẹn giờ: chỉ thêm người qua bộ lọc an toàn ngay bây giờ (`excluded` đếm theo lý do), lọc lại lúc bắt đầu. Đang chạy hoặc tạm dừng: lọc an toàn ngay, người không đạt được ghi lý do.

Tham sốỞ đâuMô tả
campaignIdđường dẫnMã chiến dịch.
curl -X POST "/api/public/v1/campaigns/{campaignId}/enroll" \
  -H "Authorization: Bearer waa_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"contactIds":["7b024e06-36ae-46d0-91d1-2185cd71837f"]}'

Trả về 200

{
  "data": {
    "added": 1,
    "enrolled": 1,
    "alreadyIn": 0,
    "excluded": {},
    "unknown": []
  }
}

Webhook — cách nhận

Thêm địa chỉ https trong Cài đặt → Kết nối AI & API → Webhook và chọn sự kiện. WA Auto gửi POST JSON:

{
  "specversion": "1.0",
  "id": "b6f1c2d0-6c55-4c1e-9a43-5d1d3b1e7a10",
  "type": "message.received",
  "version": 1,
  "time": "2026-10-17T07:28:01.000Z",
  "source": "wa-auto/<workspace-id>",
  "datacontenttype": "application/json",
  "data": {
    "…": "…"
  },
  "event": "message.received",
  "at": "2026-10-17T07:28:01.000Z"
}

Thân theo CloudEvents 1.0. version là phiên bản hình dạng data của loại sự kiện — đổi hình dạng thì tăng phiên bản. event và at là tên cũ của type và time, vẫn gửi trong bản này và sẽ bỏ.

  • Trả mã 2xx trong 8 giây. Lỗi thì thử lại sau 1 phút, 5 phút, 30 phút, 2 giờ, 6 giờ.
  • Giao ít nhất một lần: có thể nhận trùng khi mạng chập chờn — lọc theo id (cũng ở header webhook-id và x-wa-auto-delivery).
  • Không bảo đảm thứ tự: sắp theo time nếu cần.
  • Hết lượt thử, hoặc webhook bị tắt, sự kiện chuyển sang Không gửi được (giữ 30 ngày) trong Cài đặt → Kết nối AI & API. Sửa máy nhận rồi bấm Gửi lại: sự kiện đi lại với đúng id cũ.
  • Sau 20 lần lỗi liên tiếp webhook tự tắt và chủ workspace nhận thông báo; bật lại thì được hỏi có gửi lại các sự kiện bị dừng không.

Kiểm chữ ký

Header x-wa-auto-signature = sha256= + HMAC-SHA256 của chuỗi <timestamp>.<body gốc> với bí mật ký; timestamp nằm ở header x-wa-auto-timestamp. Từ chối yêu cầu lệch quá 5 phút.

Cùng bí mật ký, WA Auto gửi thêm chữ ký chuẩn Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature) — kiểm bằng thư viện có sẵn:

// npm i standardwebhooks — dùng đúng bí mật ký whsec_… của webhook
import { Webhook } from "standardwebhooks";
const wh = new Webhook(secret);
const event = wh.verify(rawBody, {
  "webhook-id": req.header("webhook-id"),
  "webhook-timestamp": req.header("webhook-timestamp"),
  "webhook-signature": req.header("webhook-signature"),
}); // ném lỗi nếu sai chữ ký hoặc quá cũ

Tự kiểm — Node.js

import crypto from "node:crypto";

// Express: app.post("/wa-auto", express.raw({ type: "application/json" }), handler)
function verify(req, secret) {
  const ts = req.header("x-wa-auto-timestamp");
  const sig = req.header("x-wa-auto-signature"); // "sha256=<hex>"
  if (!ts || !sig || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(ts + "." + req.body).digest("hex");
  return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Tự kiểm — Python

import hmac, hashlib, time

def verify(headers, raw_body: bytes, secret: str) -> bool:
    ts = headers.get("x-wa-auto-timestamp")
    sig = headers.get("x-wa-auto-signature", "")
    if not ts or abs(time.time() - int(ts)) > 300:
        return False
    expected = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig, expected)

Sự kiện

message.receivedversion 1Có tin nhắn đến

{
  "id": "4f0c…",
  "conversationId": "1cc08299-a808-4936-a102-c4045341130f",
  "accountId": "a3e6f771-6ab9-4614-9165-7be3d240ffcf",
  "contactId": "7b024e06-36ae-46d0-91d1-2185cd71837f",
  "kind": "text",
  "text": "Cho mình hỏi giá combo",
  "at": "2026-10-17T07:28:00.000Z"
}

message.statusversion 1Tin đi đổi trạng thái (đã gửi / đã tới / đã xem / lỗi)

{
  "id": "9d2e…",
  "conversationId": "1cc08299-a808-4936-a102-c4045341130f",
  "accountId": "a3e6f771-6ab9-4614-9165-7be3d240ffcf",
  "contactId": "7b024e06-36ae-46d0-91d1-2185cd71837f",
  "status": "read",
  "sentBy": "api",
  "campaignId": null,
  "at": "2026-10-17T07:31:12.000Z"
}

contact.createdversion 1Khách mới được tạo

{
  "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
  "phone": "84901234567",
  "name": "Chị Lan",
  "email": null,
  "company": null,
  "source": "inbound",
  "createdAt": "2026-10-17T03:15:00.000Z"
}

contact.updatedversion 1Hồ sơ khách thay đổi

{
  "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
  "phone": "84901234567",
  "name": "Chị Lan",
  "email": "lan@example.com",
  "company": "Spa Hoa",
  "stageId": null,
  "dealValue": 2500000,
  "doNotContact": false,
  "deleted": false,
  "mergedInto": null
}

campaign.finishedversion 1Chiến dịch kết thúc

{
  "id": "e1b0…",
  "name": "Chăm sóc khách tháng 10",
  "status": "completed"
}

account.state_changedversion 1Số WhatsApp đổi trạng thái

{
  "id": "a3e6f771-6ab9-4614-9165-7be3d240ffcf",
  "phone": "84909203723",
  "label": "Sale 01",
  "sessionState": "logged_out",
  "safetyState": "normal",
  "previousSessionState": "online",
  "previousSafetyState": "normal"
}