admin@kovitel.com
Technical guide bodies are currently maintained in Vietnamese. English and Chinese versions are being prepared.

Hướng dẫn

Webhook

KOVITEL gửi POST JSON khi SIM đổi trạng thái, vượt ngưỡng data, job thất bại hoặc pool hết hạn. Mỗi delivery được ký HMAC-SHA256 và lưu lịch sử.

Cấu hình

Vào Webhook trên portal, nhập URL HTTPS và lưu. Secret được tạo một lần; lưu vào secret manager. Dùng nút Gửi test để kiểm tra end-to-end.

Sự kiện

EventKhi nào
SIM_STATUS_CHANGEDSIM đổi trạng thái thành công
SIM_DATA_THRESHOLDSIM vượt 90% hạn mức data
JOB_FAILEDJob thất bại sau khi hết retry
BATCH_QUEUEDBatch đã được đưa vào hàng đợi
POOL_EXPIREDPool data hết hạn
WEBHOOK_TESTGói test từ dashboard

Payload mẫu

SIM_STATUS_CHANGED
json
{
  "event": "SIM_STATUS_CHANGED",
  "iccid": "8984048000000000001",
  "msisdn": "0982144401",
  "status": "SUSPENDED",
  "previousStatus": "ACTIVE",
  "action": "SUSPEND",
  "channel": "REVERSE",
  "jobId": "8f3c2d1a-…",
  "occurredAt": "2026-11-06T12:00:00.000Z"
}

Headers

Request headers
http
x-kovitel-event: SIM_STATUS_CHANGED
x-kovitel-delivery-id: 97fda8be-…
x-kovitel-timestamp: 1762344000
x-kovitel-signature: sha256=<hex hmac>
content-type: application/json

Xác minh chữ ký

Input ký là `${timestamp}.${rawBody}`. Luôn đọc raw body trước khi parse JSON. Từ chối timestamp lệch quá 5 phút để chống replay.

Node.js / Express
typescript
import crypto from 'node:crypto';

const rawBody = req.rawBody.toString('utf8');
const timestamp = req.headers['x-kovitel-timestamp'];
const signature = req.headers['x-kovitel-signature'];

const expected = 'sha256=' + crypto
  .createHmac('sha256', process.env.KOVITEL_WEBHOOK_SECRET)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex');

const valid = expected.length === signature.length &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

if (!valid) return res.status(401).end();
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
  return res.status(401).end(); // replay window 5 phút
}
Python / Flask
python
import hmac, hashlib, time

expected = "sha256=" + hmac.new(
    secret.encode(),
    f"{timestamp}.{raw_body}".encode(),
    hashlib.sha256,
).hexdigest()

if not hmac.compare_digest(expected, signature):
    return 401

if abs(time.time() - int(timestamp)) > 300:
    return 401

Retry policy

  • 5 attempts với exponential backoff (5s → 80s).
  • 2xx được coi là thành công; response body không bắt buộc.
  • 4xx (trừ 408/429) là lỗi vĩnh viễn, không retry.
  • Timeout mặc định 10 giây. Nên xử lý nhanh và trả 2xx trước khi xử lý nặng.

Best practices

  • Idempotent theo x-kovitel-delivery-id; có thể nhận trùng khi retry.
  • Trả 2xx ngay, đưa event vào queue nội bộ nếu cần xử lý lâu.
  • Dùng HTTPS và xoay secret định kỳ qua nút Rotate trên portal.
  • Log delivery ID để đối soát với lịch sử delivery trong portal.