Rate limit + Idempotency
Hai cơ chế Zorio giúp bảo vệ API và hỗ trợ CRM xử lý việc gửi lại (retry) request an toàn.
Rate limit
Mục đích
- Bảo vệ hệ thống Zorio khỏi lưu lượng request quá lớn (vô tình hoặc DDoS).
- Đảm bảo công bằng giữa các tài khoản — 1 tài khoản không thể chiếm hết tài nguyên.
- Khuyến khích Khuyến khích CRM sử dụng cache và xử lý theo lô (batch) thay vì gửi quá nhiều request.
Mức giới hạn mặc định
Tính theo token (mỗi token riêng):
| Endpoint group | Limit | Window |
|---|---|---|
/api/pbx/calls/click-to-call | 10 req/phút | 60 giây trượt |
/api/pbx/* (mọi endpoint khác) | 60 req/phút | 60 giây trượt |
/api/telesales/* | 120 req/phút | 60 giây trượt |
/api/autocall/* | 120 req/phút | 60 giây trượt |
/api/auth/login | 5 req/phút | 60 giây trượt (chống brute-force) |
/api/auth/* (các endpoint khác) | 30 req/phút | 60 giây trượt |
Cần limit cao hơn?
Khách hàng enterprise có thể yêu cầu tăng giới hạn Rate Limit qua hợp đồng SLA. Liên hệ team Zorio.
Header trả về
Mọi response có header sau:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1782706011| Header | Ý nghĩa |
|---|---|
X-RateLimit-Limit | Tổng request cho phép trong window |
X-RateLimit-Remaining | Còn lại bao nhiêu request trong window |
| Thời điểm (Unix Epoch) giới hạn được đặt lại. |
Khi vượt giới hạn
HTTP 429 với body:
{ "message": "Too many requests. Retry after 25 seconds." }Header:
Retry-After: 25
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782706036Best practice CRM
Tuân thủ header Retry-After.
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After'), 10) || 60;
await sleep(retryAfter * 1000);
return retry();
}2. Exponential backoff + jitter
Tránh thundering herd khi nhiều client cùng retry:
async function callWithBackoff(fn, attempt = 0) {
try {
return await fn();
} catch (err) {
if (err.status !== 429 && err.status < 500) throw err;
if (attempt >= 5) throw err;
const baseDelay = Math.min(1000 * Math.pow(2, attempt), 30_000);
const jitter = Math.random() * 1000;
await sleep(baseDelay + jitter);
return callWithBackoff(fn, attempt + 1);
}
}3. Batch endpoint khi có
Nhiều endpoint hỗ trợ batch:
POST /api/telesales/campaigns/{id}/leads/bulk-import— import N lead 1 lầnPATCH /api/telesales/leads/bulk— update N lead 1 lầnDELETE /api/telesales/leads/bulk— xoá N lead 1 lần
Dùng batch tiết kiệm rate limit + nhanh hơn so với gọi từng request riêng lẻ.
4. Cache response
Resource ít đổi (vd caller-ids, queues, users) — cache 1-5 phút ở CRM. Không nên polling liên tục (ví dụ mỗi giây).
5. Monitor X-RateLimit-Remaining
Khi Remaining < 5 — chủ động giảm tốc độ gửi request, đừng đợi đến 0 rồi retry.
Idempotency
Vấn đề
Khi client retry 1 POST/PATCH/DELETE do timeout, làm sao đảm bảo:
- Không tạo trùng resource (vd 2 lead cùng số).
- Không double charge (vd 2 cuộc gọi cho 1 click).
- Không double action (vd disposition 2 lần cho 1 cuộc gọi).
Giải pháp Zorio: Idempotency-Key
Client gửi header Idempotency-Key: <UUID v4> với mỗi request có thể được gửi lại (retry).
curl -X POST 'https://app.zorio.vn/api/pbx/calls/click-to-call' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890' \
-d '{"from_extension":"1001","to_phone":"0987654321"}'Cách Zorio xử lý
- Lần đầu thấy
Idempotency-Key→ xử lý request bình thường, lưu response vào cache với key này (TTL 24h). - Lần sau thấy
Idempotency-Keyđã thấy → trả ngay response đã được lưu trong cache, KHÔNG xử lý lại.
Endpoint hỗ trợ Idempotency-Key
| Method + endpoint | Lý do cần |
|---|---|
POST /api/pbx/calls/click-to-call | Tránh gọi 2 lần khi sự cố mạng tạm thời |
POST /api/telesales/campaigns | Tránh tạo 2 campaign cùng tên |
POST /api/telesales/campaigns/{id}/leads | Tránh tạo 2 lead trùng |
POST /api/autocall/campaigns | Tránh tạo 2 campaign cùng tên |
POST /api/integrations/sms | Tránh gửi SMS hai lần gây phát sinh chi phí. |
Yêu cầu với Idempotency-Key
- Format: UUID v4 (
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx). - Mỗi request phải sử dụng một Idempotency-Key riêng.: KHÔNG dùng lại key cho request khác — bạn sẽ nhận response cũ.
- Thời gian lưu (TTL) là 24 giờ.: sau 24h key tự expire, retry với cùng key có thể tạo resource mới.
Sample tích hợp Node.js
const { v4: uuidv4 } = require('uuid');
async function clickToCall(fromExt, toPhone) {
const key = uuidv4();
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await api.post('/pbx/calls/click-to-call', {
from_extension: fromExt,
to_phone: toPhone,
}, {
headers: { 'Idempotency-Key': key },
});
return res.data;
} catch (err) {
if (attempt === 2) throw err;
await sleep(1000 * (attempt + 1));
}
}
}→ Dù retry 3 lần, chỉ 1 cuộc gọi được tạo.
Lưu ý
- Body request phải giống hệt giữa các lần retry (cùng
Idempotency-Key). Khác body → HTTP 422 với"message": "Idempotency-Key đã dùng với body khác.". - Endpoint không hỗ trợ Idempotency-Key sẽ bỏ qua header này — không gây lỗi.
Concurrency control
Optimistic locking với If-Match
Một số endpoint update hỗ trợ If-Match để tránh xung đột cập nhật đồng thời (race condition):
PUT /api/pbx/extensions/1001
If-Match: "etag-abc123"Nếu resource đã đổi giữa lúc client GET → ETag mismatch → HTTP 412 { "message": "Resource đã thay đổi, vui lòng GET lại." }. CRM cần lấy lại dữ liệu và áp dụng thay đổi lại..
Connection limit
Tối đa 20 kết nối đồng thời cho mỗi token.
- Vượt → HTTP 429 với body
{ "message": "Too many concurrent connections." }.
Tài liệu liên quan
- Bearer (phiên user)
- API Token
- HTTP status codes — chi tiết 429
- Webhook retry policy — idempotency phía receiver
