Đăng ký
Console › Webhooks › Thêm địa chỉ nhận, theo từng môi trường. Hoặc qua API với token ứng dụng và quyền webhooks:manage.
Một địa chỉ nhận phục vụ mọi kết nối của ứng dụng. Mỗi sự kiện mang connection_id và organization_id để bạn biết của khách nào. Khoá bí mật whsec_… chỉ hiện một lần.
Môi trường chính thức bắt buộc https:// và không chấp nhận địa chỉ mạng nội bộ. Môi trường thử nghiệm nhận http:// để bạn thử qua tunnel.
Định dạng
POST {url}
Content-Type: application/json
X-CTAS-Event: identity.confirmed
X-CTAS-Event-Id: evt_01J9…-whk_…
X-CTAS-Timestamp: 1757127672
X-CTAS-Signature: t=1757127672,v1=5f1a…
X-CTAS-Api-Version: 2026-09-01
Body là sự kiện đầy đủ:
{ "id": "evt_01J9…", "type": "member.created", "api_version": "2026-09-01",
"created_at": "…", "connection_id": "con_…", "organization_id": "org_…",
"data": { … } }
Kiểm chữ ký
Bắt buộc. Tính HMAC-SHA256 với khoá bí mật của địa chỉ nhận trên chuỗi "{timestamp}.{raw body}". Từ chối nếu lệch giờ quá 300 giây. So sánh bằng hàm thời gian hằng. Dùng raw body, đừng parse rồi stringify lại.
import crypto from 'node:crypto'
export function verifyCtas(rawBody, headers, secret) {
const ts = headers['x-ctas-timestamp']
const sig = Object.fromEntries(headers['x-ctas-signature'].split(',').map(p => p.split('=')))
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1 ?? ''))
}
function verifyCtas(string $rawBody, array $headers, string $secret): bool {
$ts = (int) ($headers['X-CTAS-Timestamp'] ?? 0);
if (abs(time() - $ts) > 300) return false;
$parts = [];
foreach (explode(',', $headers['X-CTAS-Signature'] ?? '') as $p) {
[$k, $v] = explode('=', $p, 2) + [null, null];
$parts[$k] = $v;
}
return hash_equals(hash_hmac('sha256', "{$ts}.{$rawBody}", $secret), (string) ($parts['v1'] ?? ''));
}
Bấm Gửi thử trong console để nhận một sự kiện webhook.test ngay và xem kết quả. Đây là cách nhanh nhất kiểm tra chữ ký trước khi nối thật.
Trả lời và thử lại
- Trả
2xxtrong 8 giây. Xử lý nặng thì đưa vào hàng đợi rồi trả200. - Không nhận được
2xx(hết giờ,5xx,408,429): CTAS gửi lại sau 1 giây, 5 giây, 30 giây, 2 phút, 10 phút. Sau 5 lần thì đánh dấu thất bại, bạn gửi lại được trong console. 4xxkhác: không thử lại.- Thất bại 20 lần liên tiếp: địa chỉ nhận bị tắt, chủ ứng dụng nhận email.
X-CTAS-Event-Idkhông đổi giữa các lần gửi. Hãy khử trùng theo id này. Thứ tự sự kiện không đảm bảo.
Danh sách sự kiện
| Sự kiện | Khi nào |
|---|---|
connection.created, connection.revoked | tổ chức kết nối, ngắt kết nối |
connection.compromised | refresh token cũ bị dùng lại, mọi token của kết nối đã bị thu hồi |
identity.confirmed, identity.declined, identity.expired | xác nhận bằng QR |
member.created, member.updated, member.left | nhân sự thay đổi, từ ứng dụng hay từ API |
member.avatar_updated | nhân sự đổi hoặc gỡ ảnh đại diện; chỉ có member_id và has_avatar |
attendance.checked_in, attendance.checked_out | chấm công |
leave.requested, leave.decided | đơn nghỉ |
process.submission.created, process.submission.reviewed, process.occurrence.missed | quy trình |
payslip.closed, payslip.reopened, payslip.verified, payslip.unverified, payslip.paid, payslip.deleted | phiếu lương đổi bước; chỉ có payslip_id, member_id, period, state — đọc số bằng payroll:read |
Không dùng webhook
GET /v1/events?since=&type=&connection=&cursor= trả cùng nội dung theo thứ tự thời gian, giữ 30 ngày. Token ứng dụng thấy mọi sự kiện của môi trường. Token kết nối chỉ thấy sự kiện của kết nối đó.
| Trường | Giá trị |
|---|---|
deliveries.status | pending, sent, failed |
CTAS Developer