Skip to content

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 groupLimitWindow
/api/pbx/calls/click-to-call10 req/phút60 giây trượt
/api/pbx/* (mọi endpoint khác)60 req/phút60 giây trượt
/api/telesales/*120 req/phút60 giây trượt
/api/autocall/*120 req/phút60 giây trượt
/api/auth/login5 req/phút60 giây trượt (chống brute-force)
/api/auth/* (các endpoint khác)30 req/phút60 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-LimitTổng request cho phép trong window
X-RateLimit-RemainingCò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:

json
{ "message": "Too many requests. Retry after 25 seconds." }

Header:

Retry-After: 25
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782706036

Best practice CRM ​

Tuân thủ header Retry-After.

js
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:

js
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ần
  • PATCH /api/telesales/leads/bulk — update N lead 1 lần
  • DELETE /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).

bash
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ý ​

  1. 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).
  2. 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 + endpointLý do cần
POST /api/pbx/calls/click-to-callTránh gọi 2 lần khi sự cố mạng tạm thời
POST /api/telesales/campaignsTránh tạo 2 campaign cùng tên
POST /api/telesales/campaigns/{id}/leadsTránh tạo 2 lead trùng
POST /api/autocall/campaignsTránh tạo 2 campaign cùng tên
POST /api/integrations/smsTrá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 ​

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 ​

Cấp phép theo điều khoản sử dụng của Zorio.