admin@kovitel.com
技术指南正文目前以越南语维护,英文和中文版本正在准备中。

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.