Skip to content

Rate limit + Idempotency

Hai cơ chế Zorio dùng để bảo vệ API + giúp CRM xử lý retry an toàn.

Rate limit

Mục đích

  • Bảo vệ hệ thống Zorio khỏi flood request (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 CRM caching + batch xử lý thay vì spam endpoint.

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ể request limit cao hơn 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
X-RateLimit-ResetEpoch giây UTC khi quota reset về Limit

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

1. Tôn trọng 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 loop call từng cái.

4. Cache response

Resource ít đổi (vd caller-ids, queues, users) — cache 1-5 phút ở CRM. Không poll mỗi giây.

5. Monitor X-RateLimit-Remaining

Khi Remaining < 5 — slow down chủ động, đừ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 retry-able.

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 cached, 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 mạng glitch
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 2 lần (kép phí)

Yêu cầu với Idempotency-Key

  • Format: UUID v4 (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx).
  • Unique per request: KHÔNG dùng lại key cho request khác — bạn sẽ nhận response cũ.
  • TTL 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ẽ ignore 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 để chống race:

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 re-fetch + re-apply changes.

Connection limit

  • Tối đa 20 connection đồng thời per 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.