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
| Event | Khi nào |
|---|---|
| SIM_STATUS_CHANGED | SIM đổi trạng thái thành công |
| SIM_DATA_THRESHOLD | SIM vượt 90% hạn mức data |
| JOB_FAILED | Job thất bại sau khi hết retry |
| BATCH_QUEUED | Batch đã được đưa vào hàng đợi |
| POOL_EXPIRED | Pool data hết hạn |
| WEBHOOK_TEST | Gó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/jsonXá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 401Retry 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.
