Bỏ qua tới nội dung chính
DuLieuCaNhan.vnDeveloper
Trang chủĐăng nhập

API & webhook

API công khai và webhook

API cho phép hệ thống của bạn (CRM, tổng đài, ứng dụng) tra cứu và ghi nhận đồng ý, đọc và cập nhật yêu cầu của chủ thể dữ liệu. Webhook gửi sự kiện từ chúng tôi sang hệ thống của bạn. Cả hai có ở gói Doanh nghiệp và Agency.

Xác thực

  • Chủ sở hữu workspace tạo khoá ở trang API & webhook của dashboard. Khoá có dạng dlcn_ và 40 ký tự chữ, số; khoá chỉ hiện đầy đủ một lần lúc tạo.
  • Gửi khoá trong header Authorization: Bearer <khoá>. API chỉ dùng cho máy chủ của bạn: không có cookie, không CORS, nên đừng đặt khoá trong trang web hay ứng dụng cài trên máy khách.
  • Mỗi khoá chỉ có các quyền bạn chọn khi tạo. Khoá thiếu quyền nhận lỗi 403, khoá sai hay đã thu hồi nhận lỗi 401.

Quyền của khoá

QuyềnCho phép
sites:readGET /ext/sites
consents:readGET /ext/consents/{consentId}, GET và POST /ext/consents/check
consents:writePOST /ext/consents, POST /ext/consents/{consentId}/links
requests:readGET /ext/requests
requests:writePATCH /ext/requests/{id}

Giới hạn và lỗi

  • Mỗi khoá được gọi tối đa 600 lần mỗi phút. Vượt thì nhận lỗi 429 kèm header Retry-After (số giây phải chờ).
  • Lỗi luôn có dạng JSON { "error": { "code": "...", "message": "..." } }; message bằng tiếng Việt, code là mã cố định để chương trình của bạn xử lý.
  • Gói Miễn phí không có API: khoá còn nhưng mọi lời gọi nhận lỗi 403 PLAN_FEATURE. Quay lại gói trả phí thì khoá dùng lại được.
  • POST /ext/consents được tính vào hạn mức lượt ghi nhận đồng ý của tháng, cùng bộ đếm với banner.
  • Gọi sai khoá quá 30 lần mỗi phút từ cùng một địa chỉ IP thì các lời gọi sai khoá tiếp theo của địa chỉ đó nhận lỗi 429 (chặn dò khoá); lời gọi đúng khoá không bị bộ đếm này chặn.
Mã lỗiHTTPÝ nghĩa
INVALID_API_KEY401Thiếu, sai dạng, không tồn tại hoặc đã thu hồi.
INSUFFICIENT_SCOPE403Khoá không có quyền của route này.
PLAN_FEATURE403Gói hiện tại của workspace không có API.
NOT_FOUND404Website, mã đồng ý hoặc yêu cầu không có trong workspace của khoá.
VALIDATION_ERROR400Dữ liệu gửi lên không hợp lệ.
SITE_NOT_PUBLISHED409Website chưa xuất bản banner nên chưa ghi nhận đồng ý bằng API được.
TOO_MANY_REQUESTS429Vượt 600 lời gọi mỗi phút.
PLAN_LIMIT403Workspace có nhiều khoá hơn số khoá của gói hiện tại và khoá này nằm ngoài số còn dùng được (thường sau khi hạ gói). Thu hồi bớt khoá hoặc nâng gói.
LINK_LIMIT409Mã đồng ý đã có đủ số liên kết tối đa. Gắn lại một liên kết đã có vẫn được chấp nhận.

Các route

Địa chỉ gốc: https://dulieucanhan.vn/api/v1. Tài liệu OpenAPI đầy đủ ở /api/v1/openapi.json (thẻ "API công khai").

  • GET/ext/sitessites:read

    Website của workspace: id, tên miền, siteKey, tên.

  • GET/ext/consents/{consentId}?siteId=consents:read

    Lựa chọn mới nhất còn hiệu lực (latest; null kèm expired là true khi bản ghi mới nhất đã hết hiệu lực), lịch sử (mới nhất trước, tối đa 50 bản ghi, không lọc theo hiệu lực) và các liên kết của một mã đồng ý.

  • GET/ext/consents/check?siteId=&category=&consentId=consents:read

    Khách đã đồng ý loại cookie này chưa. granted là true hoặc false theo bản ghi mới nhất còn hiệu lực (cùng luật với banner: cấu hình không cũ hơn mức hỏi lại đã xuất bản và chưa quá số ngày lưu của website), hoặc null khi không có bản ghi còn hiệu lực: chưa có, đã bị thay bằng mức hỏi lại mới, hoặc quá hạn. Khi đó expired là true nếu có bản ghi nhưng đã hết hiệu lực. Chúng tôi không đoán. Phản hồi gồm granted, consentId, recordedAt, configVersion, expired. Chỉ nhận consentId: email, số điện thoại hay mã khách hàng không bao giờ nằm trong URL (gửi refType hoặc ref ở đây bị từ chối với lỗi 400). category là một trong necessary, functional, analytics, marketing.

  • POST/ext/consents/checkconsents:read

    Như bản GET nhưng nhận thân JSON { siteId, category, consentId } hoặc { siteId, category, refType, ref }: đúng một trong consentId hay cặp refType và ref. Dùng bản này để tra theo email, số điện thoại hay mã khách hàng đã gắn, vì các giá trị đó không được đi trong URL.

  • POST/ext/consents/{consentId}/linksconsents:write

    Gắn mã đồng ý với một chủ thể bên ngoài (email, phone hoặc customer_id). Giá trị được chuẩn hoá rồi chỉ lưu dạng HMAC-SHA256 với khoá riêng của workspace, không lưu giá trị thô. Gọi lại với cùng nội dung không tạo thêm liên kết.

  • POST/ext/consentsconsents:write

    Ghi nhận đồng ý đã thu ngoài banner (tổng đài, giấy, backend của ứng dụng). Kênh server, offline hoặc app; kèm phiên bản câu chữ khách đã thấy và ngôn ngữ. Tự sinh consentId (UUID v4) nếu bỏ trống.

  • GET/ext/requests?siteId=&status=requests:read

    Yêu cầu của chủ thể dữ liệu, mới nhất trước, phân trang bằng cursor.

  • PATCH/ext/requests/{id}requests:write

    Đổi sang in_progress, hoặc đóng bằng done hay rejected kèm note gửi người yêu cầu. Cùng quy tắc trạng thái và cùng dòng thời gian với hộp yêu cầu của dashboard.

Trong API, bốn nhóm cookie có đúng tên như trong banner: necessary, functional, analytics và marketing; tên khác bị từ chối với lỗi 400.

Ví dụ

Kiểm tra trước khi gửi sự kiện quảng cáo

bash
curl -s -X POST "https://dulieucanhan.vn/api/v1/ext/consents/check" \
  -H "Authorization: Bearer $DLCN_API_KEY" -H "Content-Type: application/json" \
  -d '{"siteId":"SITE_ID","category":"marketing","refType":"email","ref":"khach@example.vn"}'

# {"granted":true,"consentId":"3f0c…","recordedAt":"2026-10-27T03:12:45.000Z","configVersion":4,"expired":false}
# {"granted":null,"consentId":null,"recordedAt":null,"configVersion":null,"expired":false}   <- chưa có bản ghi: không gửi
# {"granted":null,"consentId":"3f0c…","recordedAt":"2025-09-01T08:00:00.000Z","configVersion":2,"expired":true}   <- hết hiệu lực: không gửi

Ghi nhận đồng ý thu qua tổng đài

bash
curl -s -X POST https://dulieucanhan.vn/api/v1/ext/consents \
  -H "Authorization: Bearer $DLCN_API_KEY" -H "Content-Type: application/json" \
  -d '{"siteId":"SITE_ID","channel":"offline",
        "choices":{"functional":true,"analytics":false,"marketing":true},
        "evidence":{"textVersion":"kich-ban-tong-dai-2026-10","locale":"vi"},
        "link":{"system":"crm","refType":"customer_id","ref":"KH-00123"}}'

Webhook

  • Chủ sở hữu thêm điểm nhận ở trang API & webhook: địa chỉ https tới một tên miền công khai (cổng 443 hoặc 8443) và các sự kiện muốn nhận. Mỗi điểm nhận có một bí mật ký, hiện một lần khi tạo và đổi được bất cứ lúc nào (bí mật cũ hết hiệu lực ngay).
  • Chúng tôi gửi POST với thân JSON { "id", "type", "createdAt", "workspaceId", "data" }. Điểm nhận trả mã 2xx trong 10 giây là thành công; mã khác, quá hạn hay lỗi kết nối thì sự kiện được thử lại.
  • Lịch thử lại: sau 1 phút, 5 phút, 25 phút, 100 phút, 4 giờ, 8 giờ rồi khoảng 9 giờ 43 phút; tối đa 8 lần trong chưa đầy 24 giờ. Hết lượt thì chủ workspace nhận cảnh báo và thư. Trong lúc điểm nhận đang lỗi, chúng tôi chỉ thử một sự kiện mỗi 5 phút; các sự kiện khác chờ và được gửi tiếp khi điểm nhận trả lời được.
  • Mỗi sự kiện có một mã id cố định cho mỗi điểm nhận: cùng id có thể tới nhiều hơn một lần khi thử lại, nên hệ thống của bạn cần bỏ qua id đã xử lý.
  • Chúng tôi không theo chuyển hướng, và trước mỗi lần gửi kiểm tra tên miền không trỏ về mạng nội bộ hay địa chỉ không công khai; điểm nhận như vậy bị từ chối.
  • Nhật ký giao giữ 30 ngày, chỉ có trạng thái, số lần thử, mã trả lời và thời gian; không lưu nội dung trả lời của bạn.

Sự kiện

Sự kiệnGửi khi
consent.recordedCó bản ghi đồng ý mới của một mã đã gắn ít nhất một liên kết.
consent.withdrawnMột bản ghi chuyển phân tích hoặc quảng cáo từ đồng ý sang không đồng ý (mọi mã, kể cả chưa gắn liên kết).
dsr.createdCó yêu cầu mới của chủ thể dữ liệu.
dsr.updatedYêu cầu được trả lời, gia hạn, đổi trạng thái hay đóng.
dsr.due_soonYêu cầu còn không quá 48 giờ tới hạn (một lần cho mỗi hạn).
scan.completedMột lượt quét website của workspace hoàn tất (có thể chậm tới 10 phút).
alert.openedMột cảnh báo mới mở trên dashboard.

Dữ liệu sự kiện chỉ có mã, loại, trạng thái và hạn; không có tên, email, số điện thoại hay lời nhắn của người yêu cầu, và không có email, số điện thoại hay mã khách hàng của người đã gắn liên kết. Cần chi tiết thì gọi API bằng khoá có quyền tương ứng.

json
{
  "id": "9b1f2c3e-5a6d-5e7f-8a90-b1c2d3e4f501",
  "type": "consent.withdrawn",
  "createdAt": "2026-10-27T03:12:45.000Z",
  "workspaceId": "WORKSPACE_ID",
  "data": { "siteId": "SITE_ID", "consentId": "3f0c…", "withdrawn": ["marketing"], "links": [{ "system": "crm", "refType": "customer_id" }] }
}

Kiểm tra chữ ký

  • Mỗi lời gọi có header DLCN-Signature dạng t=<unix>,v1=<hex>. v1 là HMAC-SHA256 của chuỗi "<t>.<thân gốc>" với khoá là bí mật ký của điểm nhận.
  • Tính lại từ thân gốc (đúng từng byte, trước khi phân tích JSON), so sánh bằng hàm so sánh thời gian không đổi, và từ chối khi t lệch giờ hiện tại quá 5 phút để chặn phát lại.
  • Các header khác: DLCN-Event-Id (cùng id với thân), DLCN-Event-Type và DLCN-Delivery-Attempt (lần thử thứ mấy).

Node.js

js
import { createHmac, timingSafeEqual } from 'node:crypto'

// rawBody: chuỗi thân gốc (ví dụ express.raw), signature: header DLCN-Signature
export function verify(rawBody, signature, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(signature.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(parts.v1 ?? '')
  return a.length === b.length && timingSafeEqual(a, b)
}

PHP

php
<?php
// $rawBody = file_get_contents('php://input'); $header = $_SERVER['HTTP_DLCN_SIGNATURE'] ?? '';
function dlcn_verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    $parts = [];
    foreach (explode(',', $header) as $p) {
        $kv = explode('=', $p, 2);
        if (count($kv) === 2) { $parts[$kv[0]] = $kv[1]; }
    }
    $t = isset($parts['t']) ? (int) $parts['t'] : 0;
    if ($t === 0 || abs(time() - $t) > $tolerance) { return false; }
    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1'] ?? '');
}

Lưu ý khi dùng

  • Giữ khoá API và bí mật ký như mật khẩu: đặt trong biến môi trường của máy chủ, không đưa vào mã nguồn hay trang web.
  • Nghi ngờ lộ khoá thì thu hồi ngay ở dashboard; mọi lần tạo, thu hồi khoá và đổi trạng thái yêu cầu qua API đều ghi vào nhật ký hoạt động của workspace.
  • Kết quả kiểm tra đồng ý là bản ghi của khách tại thời điểm ghi nhận. Việc có được phép xử lý dữ liệu cho một mục đích cụ thể hay không còn phụ thuộc cách bạn dùng dữ liệu; hãy hỏi luật sư khi cần.