API & Webhook cho đối tác · v2026.10

Tích hợp eNotary vào hệ thống của bạn

Gửi tài liệu PDF sang Phòng công chứng, trao đổi với cán bộ, và nhận kết quả đã ký ngay khi xong. Chỉ cần gọi API và nhận webhook.

Dev · thử nghiệm
https://api-dev.enotaryid.com/api/integration
Production · dữ liệu thật
https://api.enotaryid.com/api/integration
Xác thực: header X-API-Key Định dạng: JSON Giới hạn: 60 yêu cầu/phút/khóa Cập nhật: 22/10/2026

1Giới thiệu & luồng tổng quan

Hệ thống của bạn gọi API để gửi tài liệu. eNotary gọi lại webhook của bạn khi có tin mới.

→

Chiều vào: bạn gọi eNotary

Gửi PDF, xem danh sách, xem chi tiết, tải file, viết trao đổi. Mọi yêu cầu kèm header X-API-Key.

←

Chiều ra: eNotary gọi bạn

Khi tài liệu được dùng, ký xong, bị từ chối hoặc có trao đổi mới, eNotary gửi webhook (HTTP POST, có chữ ký) tới địa chỉ của bạn.

Luồng nghiệp vụ tổng quan Đối tác gửi tài liệu qua API, Phòng công chứng xử lý và ký, eNotary gửi webhook về cho đối tác. HỆ THỐNG ĐỐI TÁC eNOTARY (API + WEBHOOK) PHÒNG CÔNG CHỨNG ① Gửi tài liệu PDF POST /documents ② Lưu thành bộ tài liệu trạng thái READY ③ Thấy bộ trong mục Tài liệu chia sẻ ④ Kéo file vào hồ sơ công chứng viên xử lý và ký số ⑤ Ghi nhận kết quả file đã ký trả về đúng bộ cũ ⑥ Gửi webhook (có chữ ký) shared_file.completed ⑦ Nhận webhook, tải kết quả GET /files/{uuid}/download webhook tùy chọn: shared_set.created shared_file.used (webhook khi file được kéo vào hồ sơ)
Phòng công chứng làm việc trên giao diện web của eNotary. Bạn chỉ cần gọi API và nhận webhook.

Kịch bản có từ chối, gửi lại và trao đổi

Hình trên là đường đi thuận lợi. Thực tế hai bên thường qua lại vài lượt trước khi ký. Hình dưới là một kịch bản đầy đủ: Phòng công chứng từ chối một file, bạn gửi bản mới, hai bên trao đổi, rồi Phòng công chứng công chứng và ký.

Kịch bản có từ chối, gửi lại và trao đổi Đối tác gửi file A, Phòng công chứng từ chối, đối tác gửi file A′ mới vào cùng bộ, hai bên trao đổi, Phòng công chứng kéo A′ vào hồ sơ và ký, eNotary gửi webhook ở từng bước. Hệ thống đối tác eNotary (API + webhook) Phòng công chứng ① POST /documents (file A) bộ READY, file A: Chờ sử dụng shared_set.created · shared_file.created (tùy chọn đăng ký) ② Từ chối file A, ghi lý do shared_file.rejected có reason, actor · A: Bị từ chối (34) ③ POST /documents/{uuid}/files fileType ORIGINAL · file A′ (uuid mới) shared_file.created (A′) (tùy chọn đăng ký) A′ hiện trong cùng bộ ④ Viết trao đổi trên web ví dụ: đề nghị bổ sung thông tin shared.comment_created author.type = "ORG" ⑤ POST /documents/{uuid}/comments nội dung Markdown shared.comment_created author.type = "API" → bạn bỏ qua thông báo trong ứng dụng ⑥ Kéo file A′ vào hồ sơ shared_file.used A′: Đang dùng (32) ⑦ Công chứng, ký số xong shared_file.completed A′: Hoàn thành (33), documentType 14 ⑧ GET /files/{uuid của A′}/download tải bản đã ký
Mũi tên cam là webhook eNotary gửi cho bạn. Mũi tên xanh là API bạn gọi, hoặc thao tác của Phòng công chứng trên web. Mũi tên đỏ là thao tác từ chối.

Bản gửi lại là một file mới

File A bị từ chối là trạng thái cuối, không sửa hay thay nội dung được. Bản mới A′ có uuid riêng và không tự liên kết với A. Hãy đặt code có quy ước (ví dụ HD-001, HD-001-v2) để tự đối chiếu.

Từ chối cả bộ thì phải tạo bộ mới

Khi nhận shared_set.rejected, bộ dừng hẳn và không thêm file được nữa (API trả 409). Hãy tạo bộ mới bằng 4.1.

Phát hiện sai sau khi file đã vào hồ sơPhòng công chứng chỉ từ chối được file còn "Chờ sử dụng". Nếu file đã ở "Đang dùng" mà hồ sơ ký bị hủy, bạn nhận shared_file.sign_rejected (có reason, dossier). File trở lại để chọn vào hồ sơ khác, nội dung không đổi. Nếu cần sửa nội dung, hai bên trao đổi rồi bạn gửi file mới như bước ③.

Vài từ cần biết

TừNghĩa
Bộ tài liệuMột "hồ sơ" bạn gửi sang. Chứa nhiều file PDF. Có hai loại: công chứng (shared-notary-set) và chứng thực (shared-cert-set).
Tài liệu / fileMột file PDF nằm trong bộ. Mỗi file có uuid riêng.
Phòng công chứngĐơn vị nhận hồ sơ của bạn trên eNotary. Mỗi khóa API gắn với đúng một đơn vị.
WebhookeNotary tự gọi một địa chỉ HTTPS của bạn khi có sự kiện mới.
Mã của bạn (code)Mã bạn tự đặt cho bộ hoặc file để đối chiếu với hệ thống của mình. Hệ thống lưu thành externalCode.

2Bắt đầu nhanh

Từ chưa có gì đến gửi được bộ tài liệu đầu tiên trong vài phút.

  1. Xin API key Liên hệ eNotary để được cấp khóa (dạng enk_…). Khóa gắn với tài khoản đối tác của bạn trong một Phòng công chứng.
    Giữ kín khóaKhóa chỉ hiện một lần lúc cấp. Hãy cất vào nơi lưu bí mật. Mất khóa thì xin cấp khóa mới. Không để khóa trong mã nguồn công khai hay trong URL.
  2. Chọn địa chỉ gốc (base URL) Dùng Dev để thử, dùng Production khi chạy thật. Nút Dev / Production ở góc trên sẽ đổi địa chỉ trong mọi ví dụ trên trang này.
    Môi trườngBase URL
    Devhttps://api-dev.enotaryid.com/api/integration
    Productionhttps://api.enotaryid.com/api/integration
    Đừng dùng địa chỉ trang web quản trịAPI chỉ chạy ở api-dev.enotaryid.com và api.enotaryid.com. Địa chỉ bạn mở trên trình duyệt để đăng nhập (ví dụ admin-dev.enotaryid.com) là trang web, không phải API: gọi POST vào đó sẽ nhận 405 Not Allowed dạng trang HTML của nginx, và yêu cầu không tới được eNotary.
  3. Gọi thử một yêu cầu Lấy danh sách bộ tài liệu. Nếu nhận được JSON thay vì lỗi 401 là khóa đã dùng được.
    curl "https://api-dev.enotaryid.com/api/integration/documents?page=1&pageSize=5" \
      -H "X-API-Key: enk_xxxxxxxx"
    {
      "statusCode": 200,
      "data": {
        "items": [],
        "total": 0,
        "page": 1,
        "pageSize": 5,
        "metadata": { "tenantCode": "ORG-DEMO" }
      },
      "timestamp": "2026-10-12T08:00:00.000Z"
    }
  4. Gửi bộ tài liệu đầu tiên Cách dễ nhất là gửi file thật bằng multipart.
    curl -X POST "https://api-dev.enotaryid.com/api/integration/documents/upload" \
      -H "X-API-Key: enk_xxxxxxxx" \
      -F "code=HD-2026-001" \
      -F "customerName=Nguyễn Văn A" \
      -F "files=@hop-dong.pdf"
    Ghi lại data.uuid trong phản hồi. Đó là mã của bộ. Bạn sẽ dùng nó để thêm file và viết trao đổi.
    Cần tài khoản đối tácMuốn tạo bộ hoặc thêm file, khóa phải gắn với tài khoản đối tác. Nếu không, bạn nhận lỗi 403. Khóa chỉ gắn tài khoản nội bộ thì chỉ đọc được.
  5. Nhận kết quả Cách 1: đăng ký webhook để eNotary báo cho bạn. Cách 2: gọi chi tiết bộ định kỳ. Khi file ký xong, bạn tải bản đã ký bằng tải file.

3Quy ước chung

Những điều đúng cho mọi API bên dưới. Đọc một lần là đủ.

Header

TênBắt buộcGiá trị
X-API-KeyCóKhóa API của bạn, ví dụ enk_xxxxxxxx. Đây là cách xác thực duy nhất.
Content-TypeCó (khi có body)application/json cho API JSON. multipart/form-data cho API upload (thư viện HTTP thường tự thêm).

Khóa hết hạn, bị thu hồi hoặc sai sẽ nhận 401. Mỗi khóa chỉ thấy dữ liệu của đúng một Phòng công chứng. Đọc dữ liệu của đơn vị khác sẽ nhận 404 hoặc 403.

Dạng phản hồi

Phản hồi thành công luôn bọc trong một "phong bì" gồm statusCode, data, timestamp. Mọi ví dụ trong tài liệu này hiển thị đủ phong bì.

{
  "statusCode": 200,
  "data": { },
  "timestamp": "2026-10-12T08:00:00.000Z"
}
Lưu ý về mã trạng tháistatusCode trong body luôn là 200. Mã HTTP thật nằm ở dòng trạng thái của phản hồi (ví dụ 201 khi tạo mới). Hãy dựa vào mã HTTP. Riêng API tải file trả về dữ liệu file thô, không có phong bì.

Khi lỗi, phản hồi có dạng sau (không có data):

{
  "statusCode": 400,
  "message": "Provide one of: id, uuid, code",
  "error": "Bad Request"
}

message có thể là một chuỗi hoặc một mảng chuỗi (khi nhiều trường sai). Hai lỗi có thêm thông tin riêng: 409 kèm existingId, và 400 "File validation failed" kèm danh sách errors. Xem Mã lỗi.

Các loại mã: id, uuid, code, externalCode

TênVí dụLà gì
id1001Số nội bộ của eNotary. Ít dùng. Chỉ API chi tiết bộ nhận ?id=.
uuid3f2b8c1e-…Mã định danh dài của bộ hoặc file. Dùng trong đường dẫn như /documents/{uuid}/files.
code (trong phản hồi)NS-26-000042
NS-26-000042.1
Mã do eNotary sinh. File có dạng "mã bộ + số thứ tự".
externalCodeHD-2026-001Mã của bạn, là giá trị bạn gửi ở trường code lúc tạo. Là null nếu bạn không gửi.
Cẩn thận với tên code
  • Khi tạo, code là mã của bạn (lưu thành externalCode).
  • GET /documents và GET /documents/detail: ?code= tìm theo mã của bạn.
  • GET /files/detail: ?code= tìm theo mã hệ thống của file. Muốn tìm theo mã của bạn, dùng ?externalCode=.

Phân trang

Tham sốMặc địnhGiới hạnDùng ở
page1≥ 1Danh sách bộ, danh sách trao đổi
pageSize20 (danh sách bộ)
50 (trao đổi)
tối đa 200 (giá trị lớn hơn bị hạ xuống 200)Danh sách bộ, danh sách trao đổi

Danh sách bộ trả items, total, page, pageSize. Danh sách trao đổi trả rows và count (tổng số). Danh sách bộ sắp xếp mới nhất trước. Danh sách trao đổi sắp xếp cũ nhất trước.

Ngày giờ

Mọi thời điểm đều theo ISO 8601, múi giờ UTC, ví dụ 2026-10-12T08:00:00.000Z. Giờ Việt Nam (UTC+7) bằng giờ này cộng 7 tiếng. Khi gửi (ví dụ notaryRequestAt, createdFrom), bạn có thể dùng định dạng này, hoặc có kèm múi giờ như 2026-10-12T15:00:00+07:00.

Giới hạn file

MụcGiới hạn
Loại fileChỉ PDF. Hệ thống kiểm tra phần đầu file (%PDF-), không chỉ tin mimeType hay đuôi file.
Số file mỗi lần gửiTối đa 20 (tạo bộ hoặc thêm file).
Dung lượng mỗi fileTối đa 150 MB (bằng giới hạn tải file trên web eNotary). Áp dụng cho cả ba cách gửi: upload, base64, url.
Tổng dung lượng mỗi lần gửiTối đa 300 MB (cộng tất cả file trong một yêu cầu).
Body JSONTối đa 150 MB cho cả body. Base64 làm file to thêm khoảng 33%, nên một body JSON chỉ chứa được khoảng 110 MB file thật.
File lấy từ URL (type: "url")Phải là https, trỏ ra địa chỉ công khai (chặn địa chỉ nội bộ). Chờ tối đa 15 giây, theo tối đa 3 lần chuyển hướng. Server tải ngay trong lúc gọi API.
Độ dài chữfileName ≤ 255, title ≤ 500, code ≤ 255. Xem từng API.
Chọn cách gửi theo cỡ file Dưới 110 MB: dùng cách nào cũng được.
Từ 110 MB đến 150 MB: dùng API upload (multipart/form-data, xem 4.2, 5.2) hoặc gửi type: "url". Đừng nhét vào base64 vì body JSON sẽ vượt 150 MB.
Trên 150 MB: không nhận. Hãy tách file hoặc nén PDF trước khi gửi.

Quy tắc "tất cả hoặc không gì": nếu một file lỗi, cả yêu cầu bị từ chối và không có gì được tạo.

Giới hạn tần suất

Phạm viGiới hạnKhi vượt
Mỗi API key60 yêu cầu / phút429 Too Many Requests
Rate limit exceeded (60 req/min)
hoặc (600 req/min)
Mỗi Phòng công chứng (cộng mọi khóa)600 yêu cầu / phút

Hệ thống đếm theo cửa sổ 1 phút. Nhận 429 thì chờ rồi gọi lại, tăng dần thời gian chờ (1s, 2s, 4s…). Gọi tuần tự thay vì bắn song song thật nhiều. Giá trị trên là mặc định và có thể được cấu hình theo từng môi trường.

4Bộ tài liệu

Tạo một bộ, xem danh sách và xem chi tiết. Một bộ chứa nhiều file PDF.

Loại bộecmClassCodeecmClassIdGhi chú
Công chứng (mặc định)shared-notary-set21Dùng đủ các trường.
Chứng thựcshared-cert-set22Không dùng contractType, assetInfo, notaryRequestAt. Hệ thống bỏ qua và trả null.

Gửi ecmClassCode hoặc ecmClassId. Nếu gửi cả hai, ecmClassId được dùng. Không gửi thì mặc định là công chứng.

4.1 Tạo bộ tài liệu (JSON)

POST/api/integration/documents

Tạo bộ mới. File gửi ngay trong JSON, dưới dạng base64 hoặc url. Bộ vừa tạo ở trạng thái READY và hiện ngay cho Phòng công chứng.

File là tùy chọnBạn có thể tạo bộ rỗng (không gửi files) rồi thêm file sau bằng POST /documents/{uuid}/files với fileType: "ORIGINAL".
Cần tài khoản đối tácKhóa phải gắn với tài khoản đối tác. Nếu không: 403.

Body

TênKiểuBắt buộcMô tả
codestringCóMã của bạn, tối đa 255 ký tự. Phải duy nhất trong Phòng công chứng. Trùng thì 409.
ecmClassCodestringKhôngshared-notary-set hoặc shared-cert-set.
ecmClassIdnumberKhông21 hoặc 22.
customerNamestringKhôngHọ tên khách hàng, tối đa 500 ký tự.
customerIdCodestringKhôngSố CCCD / giấy tờ của khách, tối đa 64 ký tự.
contractTypestringKhôngLoại hợp đồng, tối đa 64 ký tự. Chỉ cho bộ công chứng.
assetInfostringKhôngThông tin tài sản. Chỉ cho bộ công chứng.
notaryRequestAtstring (ISO 8601)KhôngNgày giờ muốn công chứng. Chỉ cho bộ công chứng.
filesarrayKhôngTối đa 20 phần tử. Xem bảng bên dưới. Bỏ trống để tạo bộ rỗng.

Mỗi phần tử trong files

TênKiểuBắt buộcMô tả
typestringCóbase64 hoặc url.
base64DatastringCó nếu type=base64Nội dung PDF mã hóa base64. Chấp nhận cả dạng data:application/pdf;base64,….
urlstringCó nếu type=urlĐường dẫn https tới file PDF. Xem giới hạn.
fileNamestringCóTên file, tối đa 255 ký tự.
codestringKhôngMã của bạn cho file này, tối đa 255 ký tự. Trả lại ở externalCode.
titlestringKhôngTên hiển thị, tối đa 500 ký tự. Bỏ trống thì dùng fileName.
mimeTypestringKhôngNếu gửi, phải là application/pdf.
sizenumberKhôngDung lượng (byte), chỉ để tham khảo. Hệ thống tự đo dung lượng thật.

Ví dụ yêu cầu

curl -X POST "https://api-dev.enotaryid.com/api/integration/documents" \
  -H "X-API-Key: enk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "HD-2026-001",
  "ecmClassCode": "shared-notary-set",
  "customerName": "Nguyễn Văn A",
  "customerIdCode": "079123456789",
  "contractType": "Công chứng mua bán nhà đất",
  "notaryRequestAt": "2026-10-15T08:00:00.000Z",
  "files": [
    {
      "type": "base64",
      "base64Data": "JVBERi0xLjQK...",
      "fileName": "cccd.pdf",
      "code": "FILE-A",
      "title": "CCCD bản sao"
    },
    {
      "type": "url",
      "url": "https://files.example.vn/hop-dong.pdf",
      "fileName": "hop-dong.pdf",
      "code": "FILE-B",
      "title": "Hợp đồng"
    }
  ]
}'
201 Phản hồi thành công
{
  "statusCode": 200,
  "data": {
    "id": 1001,
    "code": "NS-26-000042",
    "externalCode": "HD-2026-001",
    "uuid": "3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34",
    "ecmClassId": 21,
    "ecmClassName": "Bộ tài liệu công chứng chia sẻ",
    "documentType": 17,
    "partnerStatus": "READY",
    "signStatus": "NOT_SIGNED",
    "status": 1,
    "files": [
      {
        "id": 2001,
        "uuid": "9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12",
        "code": "NS-26-000042.1",
        "externalCode": "FILE-A",
        "fileName": "cccd.pdf",
        "title": "CCCD bản sao",
        "documentType": 13,
        "section": "ORIGINAL",
        "status": 1,
        "size": 12345,
        "mimeType": "application/pdf"
      },
      {
        "id": 2002,
        "uuid": "b47a0e63-8c15-4f2d-93be-1d6a7c2e5f08",
        "code": "NS-26-000042.2",
        "externalCode": "FILE-B",
        "fileName": "hop-dong.pdf",
        "title": "Hợp đồng",
        "documentType": 13,
        "section": "ORIGINAL",
        "status": 1,
        "size": 54321,
        "mimeType": "application/pdf"
      }
    ],
    "createdAt": "2026-10-12T08:00:00.000Z",
    "metadata": { "tenantCode": "ORG-DEMO" }
  },
  "timestamp": "2026-10-12T08:00:00.000Z"
}

Các trường trong phản hồi

TênKiểuMô tả
Bộ
idnumberSố nội bộ của bộ.
codestringMã bộ do eNotary sinh.
externalCodestring | nullMã của bạn (trường code bạn đã gửi).
uuidstringMã định danh của bộ. Lưu lại để thêm file, viết trao đổi.
ecmClassId, ecmClassNamenumber, stringLoại bộ: 21 công chứng, 22 chứng thực, kèm tên hiển thị.
documentTypenumberLuôn là 17 (bộ tài liệu chia sẻ).
partnerStatusstringREADY sau khi tạo. Xem Trạng thái.
signStatusstringHiện luôn là NOT_SIGNED. Đừng dựa vào trường này để biết đã ký. Hãy dùng trạng thái của file hoặc webhook.
statusnumberMã trạng thái nội bộ của bộ lúc tạo. Dùng partnerStatus thay thế.
createdAtstringThời điểm tạo (UTC).
metadata.tenantCodestring | nullMã Phòng công chứng sở hữu bộ.
Mỗi phần tử trong files
id, uuidnumber, stringSố nội bộ và mã định danh của file. Dùng uuid để tải file, viết trao đổi.
codestringMã file do eNotary sinh, dạng "mã bộ.số thứ tự" (bắt đầu từ 1).
externalCodestring | nullMã của bạn cho file (trường files[].code).
fileName, titlestringTên file và tên hiển thị.
documentType, sectionnumber, string13 và ORIGINAL (Tài liệu gốc). Xem các khối tài liệu.
statusnumberTrạng thái file. 1 = Chờ sử dụng.
size, mimeTypenumber, stringDung lượng (byte) và application/pdf.

Lỗi thường gặp

MãKhi nào
400Thiếu code, sai kiểu dữ liệu, quá 20 file, ecmClassId hoặc ecmClassCode không hợp lệ, hoặc có file lỗi (kèm danh sách errors, xem Mã lỗi).
401Thiếu hoặc sai X-API-Key.
403Khóa chưa gắn tài khoản đối tác.
409Mã code đã có trong Phòng công chứng. Phản hồi kèm existingId.
429Vượt giới hạn tần suất.
{
  "message": "A shared set with code \"HD-2026-001\" already exists for this organization",
  "existingId": 1001
}

Muốn gửi lại an toàn khi bị mất kết nối: gọi chi tiết bộ theo ?code= trước. Nếu đã có bộ thì dùng luôn, không tạo lại.

4.2 Tạo bộ tài liệu (multipart)

POST/api/integration/documents/upload

Giống 4.1 nhưng gửi file trực tiếp bằng multipart/form-data. Phù hợp khi file lớn hoặc bạn không muốn mã hóa base64. Cần tài khoản đối tác như 4.1.

Các trường form

TênKiểuBắt buộcMô tả
codetextCóMã của bạn, tối đa 255 ký tự, duy nhất trong Phòng công chứng.
ecmClassCode / ecmClassIdtextKhôngNhư 4.1.
customerName, customerIdCode, contractType, assetInfo, notaryRequestAttextKhôngNhư 4.1 (cùng giới hạn độ dài).
filesfileKhôngLặp lại trường này cho mỗi file. Tối đa 20 file, mỗi file tối đa 150 MB, chỉ PDF.
titlestext (mảng JSON)KhôngTên hiển thị, khớp theo thứ tự file. Ví dụ ["CCCD", "Hợp đồng"]. Bỏ trống thì dùng tên file.
codestext (mảng JSON)KhôngMã của bạn cho từng file, khớp theo thứ tự file. Ví dụ ["FILE-A", "FILE-B"].
titles và codes khớp theo vị tríGửi 3 file mà codes chỉ có 2 phần tử thì file thứ 3 không có mã. Chuỗi JSON hỏng bị bỏ qua mà không báo lỗi.
Gửi bằng PostmanChọn Body → form-data. Mỗi file là một dòng có key đúng là files, đổi kiểu sang File; nhiều file thì thêm nhiều dòng cùng key files. Không đặt key dạng files[0][file] hay files[0][title]: hệ thống không nhận và trả lỗi 400. Tên hiển thị gửi chung trong một dòng titles với giá trị là mảng JSON, ví dụ ["Hợp đồng vay vốn"]. Đừng quên dòng code (bắt buộc) và header X-API-Key.

Ví dụ yêu cầu

curl -X POST "https://api-dev.enotaryid.com/api/integration/documents/upload" \
  -H "X-API-Key: enk_xxxxxxxx" \
  -F "code=HD-2026-002" \
  -F "customerName=Trần Thị B" \
  -F "files=@cccd.pdf" \
  -F "files=@hop-dong.pdf" \
  -F 'titles=["CCCD bản sao", "Hợp đồng"]' \
  -F 'codes=["FILE-A", "FILE-B"]'
201 Phản hồi thành công

Giống hệt 4.1. Lỗi cũng giống, thêm hai lỗi riêng của upload: 413 khi một file quá 150 MB, và 400 khi file không phải PDF (ví dụ Route này chỉ nhận: pdf).

4.3 Danh sách bộ tài liệu

GET/api/integration/documents

Liệt kê các bộ của Phòng công chứng bạn, mới nhất trước. Có phân trang và bộ lọc. Tất cả tham số đều không bắt buộc.

Tham số query

TênKiểuBắt buộcMô tả
pagenumberKhôngTrang, bắt đầu từ 1. Mặc định 1.
pageSizenumberKhôngSố bộ mỗi trang. Mặc định 20, tối đa 200.
codestringKhôngTìm đúng theo mã của bạn.
codeLikestringKhôngTìm mã của bạn có chứa chữ này (không phân biệt hoa thường). Ví dụ HD-2026.
ecmClassCode / ecmClassIdstring / numberKhôngLọc theo loại bộ (công chứng hoặc chứng thực).
partnerStatusstringKhôngDRAFT, READY hoặc REJECTED. Giá trị khác bị bỏ qua (không lọc).
createdFromstring (ISO 8601)KhôngTạo từ thời điểm này trở đi.
createdTostring (ISO 8601)KhôngTạo đến thời điểm này.

Tham số signStatus được nhận nhưng hiện chưa có tác dụng lọc.

Ví dụ yêu cầu

curl -G "https://api-dev.enotaryid.com/api/integration/documents" \
  -H "X-API-Key: enk_xxxxxxxx" \
  --data-urlencode "codeLike=HD-2026" \
  --data-urlencode "partnerStatus=READY" \
  --data-urlencode "page=1" \
  --data-urlencode "pageSize=20"
200 Phản hồi thành công
{
  "statusCode": 200,
  "data": {
    "items": [
      {
        "id": 1001,
        "code": "NS-26-000042",
        "externalCode": "HD-2026-001",
        "uuid": "3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34",
        "ecmClassId": 21,
        "ecmClassName": "Bộ tài liệu công chứng chia sẻ",
        "customerName": "Nguyễn Văn A",
        "customerIdCode": "079123456789",
        "partnerStatus": "READY",
        "signStatus": "NOT_SIGNED",
        "fileCount": 2,
        "createdAt": "2026-10-12T08:00:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20,
    "metadata": { "tenantCode": "ORG-DEMO" }
  },
  "timestamp": "2026-10-12T08:05:00.000Z"
}

Các trường của mỗi phần tử

TênKiểuMô tả
id, uuid, code, externalCodenumber, stringNhư ở các loại mã.
ecmClassId, ecmClassNamenumber, stringLoại bộ.
customerName, customerIdCodestringThông tin khách (có thể vắng nếu bạn không gửi).
partnerStatusstringDRAFT, READY hoặc REJECTED.
signStatusstringHiện luôn là NOT_SIGNED.
fileCountnumberSố file trong bộ.
createdAtstringThời điểm tạo (UTC).

4.4 Chi tiết bộ tài liệu

GET/api/integration/documents/detail

Xem một bộ kèm toàn bộ file và liên kết tải. Gửi một trong ba tham số. Nếu gửi nhiều, thứ tự ưu tiên là id, rồi uuid, rồi code. Không gửi cái nào thì 400. Bộ không tồn tại hoặc thuộc đơn vị khác thì 404.

Tham số query

TênKiểuBắt buộcMô tả
idnumberChọn 1Số nội bộ của bộ.
uuidstringChọn 1Mã định danh của bộ.
codestringChọn 1Mã của bạn (externalCode).

Ví dụ yêu cầu

curl -G "https://api-dev.enotaryid.com/api/integration/documents/detail" \
  -H "X-API-Key: enk_xxxxxxxx" \
  --data-urlencode "code=HD-2026-001"
200 Phản hồi thành công
{
  "statusCode": 200,
  "data": {
    "id": 1001,
    "code": "NS-26-000042",
    "externalCode": "HD-2026-001",
    "uuid": "3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34",
    "ecmClassId": 21,
    "ecmClassName": "Bộ tài liệu công chứng chia sẻ",
    "documentType": 17,
    "partnerStatus": "READY",
    "signStatus": "NOT_SIGNED",
    "customerName": "Nguyễn Văn A",
    "customerIdCode": "079123456789",
    "contractType": "Công chứng mua bán nhà đất",
    "assetInfo": null,
    "notaryRequestAt": "2026-10-15T08:00:00.000Z",
    "files": [
      {
        "id": 2001,
        "uuid": "9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12",
        "code": "NS-26-000042.1",
        "externalCode": "FILE-A",
        "fileName": "cccd.pdf",
        "title": "CCCD bản sao",
        "documentType": 13,
        "section": "ORIGINAL",
        "status": 1,
        "size": 12345,
        "downloadUrl": "https://storage.example/presigned-link?X-Amz-Signature=...",
        "signUrl": "https://storage.example/presigned-link?X-Amz-Signature=..."
      }
    ],
    "createdAt": "2026-10-12T08:00:00.000Z",
    "completedAt": null,
    "metadata": { "tenantCode": "ORG-DEMO" }
  },
  "timestamp": "2026-10-12T08:10:00.000Z"
}

Các trường trong phản hồi

TênKiểuMô tả
Các trường id, uuid, code, externalCode, ecmClass*, documentType, partnerStatus, signStatus, createdAt, metadata giống mục 4.1.
customerName, customerIdCodestringThông tin khách (vắng nếu bạn không gửi).
contractType, assetInfo, notaryRequestAtstring | nullChỉ có giá trị với bộ công chứng. Bộ chứng thực luôn null.
completedAtstring | nullThời điểm hoàn tất bộ. null nếu chưa hoàn tất.
files[]arrayGồm mọi file trong bộ, kể cả file do Phòng công chứng thêm (như lời chứng). Mỗi phần tử có id, uuid, code, externalCode, fileName, title, documentType, section, status, size, downloadUrl, signUrl.
files[].signUrl, downloadUrlstringLiên kết tải có chữ ký, sống một thời gian (mặc định 7 ngày). Hai trường có cùng giá trị. Cần liên kết ổn định thì dùng tải file.

5Tài liệu (file)

Thêm file vào bộ có sẵn, xem chi tiết một file và tải file về.

Các khối tài liệu trong một bộ

Mỗi file thuộc đúng một "khối", xác định bởi documentType. Phản hồi có sẵn section để bạn khỏi tự đổi số.

documentTypesectionTên khốiAi tạoÝ nghĩa
13ORIGINALTài liệu gốcBạnFile bạn gửi lên (lúc tạo bộ, hoặc thêm sau).
19CERT_STATEMENTLời chứngPhòng công chứngLời chứng chưa ghép. Bạn tải về nếu muốn tự ghép và ký. Bạn không gửi được khối này.
15MERGEDTài liệu ghépBạn hoặc Phòng công chứngĐã ghép lời chứng, chưa ký.
16SIGNEDTài liệu đã ký sốBạnĐã ghép và đã ký ở phía bạn.
14COMPLETEDĐã ký (kết quả)Hệ thốngBản kết quả sau khi công chứng viên ký xong. Bạn không gửi được khối này.
Kết quả nằm ở chính file bạn đã gửiKhi ký xong, eNotary không tạo file mới. Hệ thống ghi thêm một phiên bản mới lên file cũ và đổi documentType sang 14. uuid không đổi. Hãy tra theo uuid đó để lấy bản đã ký.
Mỗi file chỉ dùng cho một hồ sơKhi Phòng công chứng kéo file vào hồ sơ, file chuyển sang Đang dùng và không chọn được cho hồ sơ khác. Cần dùng cùng một văn bản cho nhiều hồ sơ thì hãy gửi nhiều file. Ngoại lệ: hồ sơ ký bị hủy giữa chừng thì file quay về trạng thái Ký bị từ chối và được chọn lại.

5.1 Thêm file vào bộ có sẵn (JSON)

POST/api/integration/documents/{uuid}/files

Đẩy thêm file vào một bộ đã tạo. Hay dùng để gửi bản đã ghép và đã ký của bạn (xem luồng tự ghép lời chứng), hoặc bổ sung văn bản gốc cho bộ rỗng. Cần tài khoản đối tác.

Tham số đường dẫn

TênKiểuBắt buộcMô tả
uuidstringCóuuid của bộ, không phải của file. Lấy từ phản hồi tạo bộ, từ chi tiết bộ, hoặc từ data.sharedSet.uuid trong webhook.
Đừng nhầm hai loại uuidPhản hồi tạo bộ có uuid ở hai chỗ: uuid của bộ ở ngoài cùng, và uuid của từng file trong files[]. Endpoint này cần cái ngoài cùng. Gửi nhầm uuid file sẽ nhận 404 Shared set not found.

Body

TênKiểuBắt buộcMô tả
fileTypestringCóChỉ nhận 3 giá trị:
ORIGINAL: Tài liệu gốc.
SIGNED: Tài liệu đã ký số (đã ghép và đã ký).
MERGED: Tài liệu ghép (đã ghép, chưa ký).
filesarrayCóTừ 1 đến 20 phần tử. Cấu trúc giống files ở 4.1 (type, base64Data hoặc url, fileName, code, title).

Bạn không thể tự gửi vào khối "Lời chứng" hay "Đã ký (kết quả)". API cũng không nhận documentType dạng số.

Ví dụ yêu cầu

curl -X POST "https://api-dev.enotaryid.com/api/integration/documents/3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34/files" \
  -H "X-API-Key: enk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "fileType": "SIGNED",
  "files": [
    {
      "type": "url",
      "url": "https://files.example.vn/hop-dong-da-ky.pdf",
      "fileName": "hop-dong-da-ky.pdf",
      "code": "HD-2026-001-SIGNED"
    }
  ]
}'
201 Phản hồi thành công

data là một mảng các file vừa thêm.

{
  "statusCode": 200,
  "data": [
    {
      "id": 2003,
      "uuid": "c58d2f91-4e07-4a6b-8d13-7f0e9b3a6c45",
      "code": null,
      "externalCode": "HD-2026-001-SIGNED",
      "fileName": "hop-dong-da-ky.pdf",
      "title": "hop-dong-da-ky.pdf",
      "documentType": 16,
      "section": "SIGNED",
      "status": 1,
      "size": 204800,
      "downloadUrl": "https://storage.example/presigned-link?X-Amz-Signature=...",
      "signUrl": "https://storage.example/presigned-link?X-Amz-Signature=..."
    }
  ],
  "timestamp": "2026-10-12T09:00:00.000Z"
}
Webhook khi thêm file gốcThêm file với fileType: "ORIGINAL" sẽ phát sự kiện shared_file.created giống file gửi lúc tạo bộ. Hai loại còn lại không phát sự kiện này.

Lỗi thường gặp

MãKhi nào
400fileType sai, files rỗng hoặc quá 20, file không phải PDF, file quá 150 MB, tổng quá 300 MB, URL không phải https hoặc trỏ vào địa chỉ nội bộ.
403Khóa chưa gắn tài khoản đối tác (…must be bound to a partner account to append files).
404Bộ không tồn tại hoặc thuộc đơn vị khác.
409Bộ đã bị Phòng công chứng từ chối. Không thêm file được nữa.
429Vượt giới hạn tần suất, hoặc Phòng công chứng đã dùng hết dung lượng lưu trữ.

5.2 Thêm file vào bộ có sẵn (multipart)

POST/api/integration/documents/{uuid}/files/upload

Bản multipart/form-data của 5.1. Cùng quy tắc, giới hạn và mã lỗi. uuid trong đường dẫn vẫn là uuid của bộ.

Các trường form

TênKiểuBắt buộcMô tả
fileTypetextCóORIGINAL, SIGNED hoặc MERGED.
filesfileCóLặp lại cho mỗi file. Từ 1 đến 20 file, mỗi file tối đa 150 MB, chỉ PDF.
titlestext (mảng JSON)KhôngTên hiển thị, khớp theo thứ tự file.
codestext (mảng JSON)KhôngMã của bạn cho từng file, khớp theo thứ tự file.

Ví dụ yêu cầu

curl -X POST "https://api-dev.enotaryid.com/api/integration/documents/3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34/files/upload" \
  -H "X-API-Key: enk_xxxxxxxx" \
  -F "fileType=SIGNED" \
  -F "files=@hop-dong-da-ky.pdf" \
  -F 'codes=["HD-2026-001-SIGNED"]'
201 Phản hồi thành công

Giống 5.1: data là mảng file vừa thêm. Thiếu file thì 400 At least one file is required. fileType sai thì 400 fileType must be one of: ORIGINAL, SIGNED, MERGED.

5.3 Chi tiết file

GET/api/integration/files/detail

Xem thông tin một file (không tải nội dung), kèm tóm tắt bộ chứa nó. Gửi một trong ba tham số. Thứ tự ưu tiên: uuid, rồi code, rồi externalCode.

Tham số query

TênKiểuBắt buộcMô tả
uuidstringChọn 1Mã định danh của file.
codestringChọn 1Mã hệ thống của file, ví dụ NS-26-000042.1.
externalCodestringChọn 1Mã của bạn cho file, ví dụ FILE-A.

Ví dụ yêu cầu

curl -G "https://api-dev.enotaryid.com/api/integration/files/detail" \
  -H "X-API-Key: enk_xxxxxxxx" \
  --data-urlencode "externalCode=FILE-A"
200 Phản hồi thành công
{
  "statusCode": 200,
  "data": {
    "id": 2001,
    "uuid": "9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12",
    "code": "NS-26-000042.1",
    "externalCode": "FILE-A",
    "fileName": "cccd.pdf",
    "title": "CCCD bản sao",
    "documentType": 13,
    "section": "ORIGINAL",
    "status": 1,
    "size": 12345,
    "mimeType": "application/pdf",
    "downloadUrl": "https://storage.example/presigned-link?X-Amz-Signature=...",
    "signUrl": "https://storage.example/presigned-link?X-Amz-Signature=...",
    "sharedSet": {
      "id": 1001,
      "uuid": "3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34",
      "code": "NS-26-000042",
      "externalCode": "HD-2026-001",
      "customerName": "Nguyễn Văn A"
    },
    "metadata": { "tenantCode": "ORG-DEMO" }
  },
  "timestamp": "2026-10-12T09:10:00.000Z"
}

Các trường của file giống mục 4.1, thêm downloadUrl và signUrl (liên kết tải có thời hạn, mặc định 7 ngày). sharedSet là tóm tắt bộ chứa file. 400 khi không gửi tham số nào, 404 File not found khi không thấy hoặc thuộc đơn vị khác.

5.4 Tải file

GET/api/integration/files/{fileUuid}/download

Tải nội dung file. Phản hồi là dữ liệu file thô (không có phong bì JSON). Luôn là phiên bản mới nhất: nếu file đã ký xong, bạn nhận bản đã ký.

Tham số đường dẫn

TênKiểuBắt buộcMô tả
fileUuidstringCóuuid của file.

Ví dụ yêu cầu

curl -L "https://api-dev.enotaryid.com/api/integration/files/9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12/download" \
  -H "X-API-Key: enk_xxxxxxxx" \
  -o ket-qua.pdf
200 Phản hồi thành công

Header Content-Type theo loại file, Content-Disposition: attachment; filename="…" (tên file được mã hóa URL). Thân phản hồi là các byte của file.

Lỗi thường gặp

MãKhi nào
404File not found hoặc File content not available.
403Access denied: file thuộc đơn vị khác.

Luồng tự ghép lời chứng

Dùng khi bạn muốn tự ghép lời chứng và tự ký ở hệ thống của mình. Khác luồng thường đúng một điểm: ai ghép lời chứng.

#Ai làmViệc làmKết quả
1BạnPOST /documents: tạo bộ kèm văn bản gốcFile ở khối Tài liệu gốc (13)
2Phòng công chứngGửi file lời chứng lên bộFile ở khối Lời chứng (19)
3eNotaryGửi webhook shared_file.cert_statement_uploadeddata.file.signUrl tải được ngay
4BạnTải lời chứng, ghép vào văn bản, ký (ngoài eNotary)-
5BạnPOST /documents/{uuid}/files với fileType: "SIGNED"File ở khối Đã ký số (16)
6Phòng công chứngChọn file (16) vào hồ sơ, công chứng viên kýWebhook shared_file.used
7eNotaryHồ sơ xong, trả file về bộFile thành khối 14, webhook shared_file.completed
Cần đăng ký sự kiện ở bước 3Bạn chỉ nhận shared_file.cert_statement_uploaded nếu đã chọn sự kiện này khi đăng ký webhook. Chưa chọn thì tự gọi chi tiết bộ và lọc files[] theo documentType = 19. File lời chứng thường là PDF, nhưng có thể là file Word (doc, docx), nên đừng giả định đuôi file.

6Trao đổi

Kênh nhắn tin hai chiều giữa bạn và Phòng công chứng, gắn với cả bộ hoặc từng file.

Không có ghi chú riêng tưMọi nội dung hai bên viết đều hiển thị cho cả hai phía. Đừng viết thứ bạn không muốn phía kia đọc.

Khi bạn gửi trao đổi, cán bộ Phòng công chứng đang xử lý bộ sẽ nhận thông báo trong ứng dụng eNotary. Bạn không cần làm gì thêm. Mỗi trao đổi mới (của bất kỳ ai) còn phát webhook shared.comment_created tới địa chỉ của bạn.

Viết nội dung bằng Markdown

Trường content là văn bản Markdown (chuẩn GitHub). Hệ thống lưu nguyên văn bạn gửi, rồi tự tạo ra HTML đã lọc sạch để hiển thị.

Bạn gõHiển thị
**chữ đậm**chữ đậm
*chữ nghiêng*chữ nghiêng
~~gạch ngang~~gạch ngang
- mục một
- mục hai
  • mục một
  • mục hai
1. bước một
2. bước hai
  1. bước một
  2. bước hai
[xem tại đây](https://example.com)xem tại đây
`ma-ho-so`ma-ho-so
```
khối mã
```
khối mã (hiển thị trong khung mã)
> trích dẫn
trích dẫn
| Cột A | Cột B |
|---|---|
| 1 | 2 |
Cột ACột B
12
# Tiêu đề
---
Tiêu đề (cấp 1 đến 6) và đường kẻ ngang.
  • Một lần xuống dòng (Enter) là một dòng mới. Không cần chừa dòng trống.
  • Tối đa 10.000 ký tự (sau khi cắt khoảng trắng đầu và cuối). Nội dung rỗng bị từ chối.
  • Nên dùng Markdown, không dùng HTML. Khi hiển thị, hệ thống chỉ giữ các thẻ định dạng an toàn; <script>, ảnh, style và thuộc tính sự kiện (onclick…) đều bị loại bỏ.
  • Chưa hỗ trợ ảnh. Dòng ảnh ![](…) không hiển thị trong contentHtml.
  • Liên kết chỉ nhận http, https, mailto. Liên kết luôn mở tab mới.

6.1 Trao đổi cấp bộ

GET/api/integration/documents/{uuid}/comments

Lấy luồng trao đổi của một bộ, cũ nhất trước.

TênVị tríBắt buộcMô tả
uuidđường dẫnCóuuid của bộ.
pagequeryKhôngMặc định 1.
pageSizequeryKhôngMặc định 50, tối đa 200.
curl "https://api-dev.enotaryid.com/api/integration/documents/3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34/comments?page=1&pageSize=50" \
  -H "X-API-Key: enk_xxxxxxxx"
200 Phản hồi thành công
{
  "statusCode": 200,
  "data": {
    "rows": [
      {
        "id": 8890,
        "content": "<p>Nhờ bổ sung CCCD mặt sau.</p>",
        "contentHtml": "<p>Nhờ bổ sung CCCD mặt sau.</p>",
        "createdAt": "2026-09-30T07:30:00.000Z",
        "author": { "type": "ORG", "name": "Nguyễn Văn A" }
      },
      {
        "id": 8892,
        "content": "Đã bổ sung **CCCD mặt sau**.\n\n- Nhờ phòng kiểm tra giúp\n- Cảm ơn",
        "contentHtml": "<p>Đã bổ sung <strong>CCCD mặt sau</strong>.</p>\n<ul>\n<li>Nhờ phòng kiểm tra giúp</li>\n<li>Cảm ơn</li>\n</ul>",
        "createdAt": "2026-10-12T08:00:00.000Z",
        "author": { "type": "API", "name": "Đối tác A" }
      }
    ],
    "count": 2
  },
  "timestamp": "2026-10-12T08:01:00.000Z"
}

Phần tử đầu là một trao đổi cũ (viết trước 12/10/2026): content là HTML đã lưu từ trước. Hiển thị thì luôn dùng contentHtml. Xem đối tượng comment.

POST/api/integration/documents/{uuid}/comments

Gửi một trao đổi mới lên bộ.

Body

TênKiểuBắt buộcMô tả
contentstringCóNội dung dạng Markdown, từ 1 đến 10.000 ký tự. Xem cách viết.
curl -X POST "https://api-dev.enotaryid.com/api/integration/documents/3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34/comments" \
  -H "X-API-Key: enk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"content": "Đã bổ sung **CCCD mặt sau**.\n\n- Nhờ phòng kiểm tra giúp\n- Cảm ơn"}'
201 Phản hồi thành công
{
  "statusCode": 200,
  "data": {
    "id": 8892,
    "content": "Đã bổ sung **CCCD mặt sau**.\n\n- Nhờ phòng kiểm tra giúp\n- Cảm ơn",
    "contentHtml": "<p>Đã bổ sung <strong>CCCD mặt sau</strong>.</p>\n<ul>\n<li>Nhờ phòng kiểm tra giúp</li>\n<li>Cảm ơn</li>\n</ul>",
    "createdAt": "2026-10-12T08:00:00.000Z",
    "author": { "type": "API", "name": "Đối tác A" }
  },
  "timestamp": "2026-10-12T08:00:00.000Z"
}

6.2 Trao đổi cấp file

Giống 6.1 nhưng gắn với một file. Dùng uuid của file. Cấu trúc yêu cầu và phản hồi y hệt cấp bộ.

GET/api/integration/files/{fileUuid}/comments

Lấy luồng trao đổi riêng của một file. Hỗ trợ page và pageSize như 6.1.

curl "https://api-dev.enotaryid.com/api/integration/files/9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12/comments" \
  -H "X-API-Key: enk_xxxxxxxx"
POST/api/integration/files/{fileUuid}/comments

Gửi trao đổi về đúng một file. Body: { "content": "…" } như 6.1.

curl -X POST "https://api-dev.enotaryid.com/api/integration/files/9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12/comments" \
  -H "X-API-Key: enk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"content": "File này em gửi nhầm bản cũ, nhờ phòng **bỏ qua** giúp."}'

Đối tượng comment

TênKiểuMô tả
idnumberMã của trao đổi. Dùng để chống xử lý trùng.
contentstringNội dung gốc dạng Markdown. Trao đổi cũ (viết trước 12/10/2026) là HTML đã lưu từ trước, dạng <p>…</p>.
contentHtmlstringHTML đã lọc sạch, hiển thị thẳng được. Chỉ chứa thẻ an toàn (đoạn, danh sách, bảng, liên kết, mã…).
createdAtstringThời điểm gửi (UTC).
author.typestringORG = Phòng công chứng. PARTNER = người của đối tác gõ trên web. API = gửi qua API này.
author.namestring | nullTên người gửi. null nếu không xác định.
Cách hiển thị đúngMuốn hiện đẹp thì dùng contentHtml. Muốn lưu hoặc cho người dùng sửa lại thì dùng content. Đừng tự biến content thành HTML mà không lọc.

Luồng trao đổi

Luồng trao đổi giữa đối tác và Phòng công chứng Đối tác gửi trao đổi bằng API, cán bộ nhận thông báo, webhook gửi về đối tác, cán bộ trả lời và webhook gửi tiếp. Hệ thống đối tác eNotary Cán bộ Phòng CC Webhook của bạn ① POST …/comments content: Markdown ② 201 + comment (contentHtml) ③ Thông báo trong ứng dụng (bạn không cần làm gì) ④ shared.comment_created author.type = "API". Bỏ qua, đừng trả lời lại. ⑤ Cán bộ trả lời trên web ⑥ shared.comment_created author.type = "ORG"
Webhook shared.comment_created bắn cho mọi trao đổi, kể cả trao đổi do chính bạn gửi qua API.
Tránh vòng lặp vô hạnNếu hệ thống của bạn tự động trả lời khi nhận webhook, hãy bỏ qua mọi sự kiện có data.comment.author.type === "API". Nếu không, mỗi câu trả lời lại sinh một sự kiện mới và lặp mãi.

Lỗi của API trao đổi

MãKhi nào
400content rỗng (content must not be empty hoặc Nội dung trao đổi không được để trống) hoặc quá 10.000 ký tự.
401Thiếu hoặc sai X-API-Key.
403Access denied: bộ hoặc file thuộc đơn vị khác.
404Shared set not found, File not found hoặc File is not part of a shared set.
429Vượt giới hạn tần suất.

7Webhook

eNotary tự gọi địa chỉ của bạn khi có chuyện xảy ra. Bạn không cần hỏi đi hỏi lại.

Tổng quan

Khi có sự kiện (file được dùng, ký xong, bị từ chối, có trao đổi mới…), eNotary gửi một yêu cầu HTTP POST chứa JSON tới địa chỉ HTTPS của bạn. Mỗi gói tin có chữ ký để bạn biết nó thật sự đến từ eNotary.

Việc bạn cần làm, theo thứ tự
  1. Nhận POST và đọc nguyên văn body (raw body).
  2. Kiểm tra chữ ký trong X-Webhook-Signature. Sai thì trả 401 và bỏ qua.
  3. Chống trùng theo X-Webhook-Id.
  4. Trả HTTP 2xx ngay. Việc nặng hãy đẩy sang hàng đợi nền của bạn.
Luồng gửi webhook và gửi lại Sự kiện được ký, xếp hàng và gửi tới đối tác. Nếu không nhận được mã 2xx thì gửi lại tối đa 6 lần. Sự kiện xảy ratrong eNotary Ký HMAC-SHA256gắn X-Webhook-Id Xếp hàng gửimỗi sự kiện một job POST tới bạnchờ tối đa 30 giây Thành côngbạn trả 2xx Chờ rồi gửi lạingay, 1s, 10s, 60s, 10 phút, 1 giờ EXHAUSTEDhết 6 lần 2xx lỗi, quá giờ hoặckhông phải 2xx gửi lại với cùng X-Webhook-Id Phía bạn: kiểm tra chữ ký → chống trùng theo X-Webhook-Id → trả 2xx ngay.
Một sự kiện có thể đến nhiều hơn một lần do gửi lại. Hãy luôn chống trùng.

Đăng ký webhook

Việc này do quản trị viên eNotary làm cho Phòng công chứng của bạn. Bạn gửi cho họ hai thứ:

  1. Địa chỉ webhook (URL) của bạn.
  2. Danh sách sự kiện bạn muốn nhận.

Họ gửi lại cho bạn Webhook Secret. Secret chỉ hiện một lần, hãy cất kỹ. Bạn dùng nó để kiểm tra chữ ký. Quản trị viên cũng có thể gửi thử một gói tin (Test ping), bật hoặc tắt webhook, đổi secret, xem nhật ký gửi và gửi lại thủ công.

Yêu cầu với địa chỉ webhook
  • Phải là HTTPS và trỏ tới địa chỉ công khai. Hệ thống chặn địa chỉ nội bộ như 10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x.
  • eNotary không đi theo chuyển hướng (301, 302…). Mã 3xx bị tính là thất bại. Hãy đưa đúng địa chỉ cuối cùng.

Bạn chỉ nhận những sự kiện đã chọn. Nếu dùng trao đổi hoặc luồng tự ghép lời chứng, nhớ chọn thêm shared.comment_created và shared_file.cert_statement_uploaded.

Header và gói tin

HeaderVí dụÝ nghĩa
Content-Typeapplication/jsonBody là JSON.
User-AgenteNotary-Webhook/1.0Tên bên gửi.
X-Webhook-Eventshared_file.usedLoại sự kiện (giống type trong body).
X-Webhook-Idevt_3f9a1c2b…Mã duy nhất của sự kiện (giống id trong body). Dùng để chống trùng.
X-Webhook-Signaturesha256=9b2c…Chữ ký HMAC-SHA256 của body, có tiền tố sha256=.

Mọi sự kiện dùng chung một "phong bì":

{
  "id": "evt_3f9a1c2b4d5e6f7a8b9c0d1e2f3a4b5c",
  "type": "shared_file.used",
  "createdAt": "2026-10-12T08:20:00.123Z",
  "metadata": { "tenantCode": "ORG-DEMO" },
  "data": { }
}
Body thật là một dòng gọnVí dụ trên được xuống dòng cho dễ đọc. Thực tế body là JSON gọn một dòng, các khóa được sắp theo thứ tự chữ cái, và chữ ký tính trên đúng chuỗi byte đó. Vì vậy hãy tính chữ ký trên raw body. Đừng đọc JSON rồi dựng lại chuỗi để tính.

Kiểm tra chữ ký

Công thức: signature = "sha256=" + HEX( HMAC_SHA256( secret, rawBody ) ). So sánh với header X-Webhook-Signature bằng phép so sánh thời gian cố định (constant-time) để tránh bị dò.

const crypto = require('crypto');
const express = require('express');
const app = express();
const SECRET = process.env.ENOTARY_WEBHOOK_SECRET;

// Cần raw body: KHÔNG dùng express.json() cho đường dẫn này
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const raw = req.body; // Buffer
  const received = req.header('X-Webhook-Signature') || '';
  const expected = 'sha256=' +
    crypto.createHmac('sha256', SECRET).update(raw).digest('hex');

  const a = Buffer.from(received);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.sendStatus(401);
  }

  const eventId = req.header('X-Webhook-Id');
  const evt = JSON.parse(raw.toString('utf8'));
  // 1) Bỏ qua nếu eventId đã xử lý rồi
  // 2) Đẩy việc nặng vào hàng đợi nền
  // 3) Trả 2xx ngay
  res.sendStatus(200);
});

Các sự kiện

Có 9 loại sự kiện. Mỗi loại có type riêng. Cột "Cấp" cho biết data.file có giá trị (file) hay null (bộ).

typeCấpKhi nào
shared_set.createdBộBộ mới được tạo xong (do bạn gọi API).
shared_file.createdFileFile được thêm vào bộ: lúc tạo bộ, hoặc thêm với fileType: "ORIGINAL". Mỗi file một sự kiện.
shared_file.cert_statement_uploadedFilePhòng công chứng gửi lời chứng lên bộ. Mỗi file một sự kiện. Dùng cho luồng tự ghép.
shared_file.usedFileFile được kéo vào hồ sơ công chứng hoặc chứng thực.
shared_file.completedFileFile đã ký xong, kết quả trả về chính file đó (khối 14).
shared_file.rejectedFilePhòng công chứng từ chối riêng một file (file còn ở trạng thái "Chờ sử dụng"). Có reason và actor.
shared_file.sign_rejectedFileHồ sơ ký bị từ chối giữa chừng. File quay lại để chọn vào hồ sơ khác. Có reason, actor, dossier.
shared_set.rejectedBộPhòng công chứng từ chối cả bộ. Có reason và actor.
shared.comment_createdBộ hoặc fileCó trao đổi mới của bất kỳ ai. data.file = null là trao đổi cấp bộ. Xem Trao đổi.

Cấu trúc payload

Mọi sự kiện có chung một dạng data. Khóa nào không áp dụng thì có giá trị null, khóa không bao giờ biến mất.

{
  "id": "evt_3f9a1c2b4d5e6f7a8b9c0d1e2f3a4b5c",
  "type": "shared_file.used",
  "createdAt": "2026-10-12T08:20:00.123Z",
  "metadata": { "tenantCode": "ORG-DEMO" },
  "data": {
    "sharedSet": {
      "id": 1001,
      "uuid": "3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34",
      "code": "NS-26-000042",
      "externalCode": "HD-2026-001",
      "customerName": "Nguyễn Văn A",
      "status": 30,
      "statusLabel": "Đã phát hành"
    },
    "file": {
      "id": 2001,
      "uuid": "9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12",
      "code": "NS-26-000042.1",
      "externalCode": "FILE-A",
      "fileName": "cccd.pdf",
      "documentType": 13,
      "size": 12345,
      "status": 32,
      "statusLabel": "Đang dùng",
      "signUrl": "https://storage.example/presigned-link?X-Amz-Signature=..."
    },
    "occurredAt": "2026-10-12T08:20:00.000Z",
    "actor": null,
    "reason": null,
    "dossier": null,
    "comment": null
  }
}

Giải thích các trường

TênKiểuMô tả
Phong bì (mọi sự kiện)
idstringMã duy nhất của sự kiện (evt_…). Dùng để chống trùng.
typestringLoại sự kiện, xem bảng trên.
createdAtstringLúc eNotary tạo gói tin (UTC).
metadata.tenantCodestring | nullMã Phòng công chứng. Hữu ích nếu một địa chỉ nhận cho nhiều đơn vị.
data.sharedSet (luôn có)
id, uuidnumber, stringSố nội bộ và mã định danh của bộ.
codestringMã bộ do eNotary sinh.
externalCodestring | nullMã của bạn. Dùng để đối chiếu với hệ thống của bạn.
customerNamestring | nullTên khách hàng.
status, statusLabelnumber, stringTrạng thái bộ: 1 Nháp, 30 Đã phát hành, 31 Từ chối. Xem Trạng thái.
data.file (null với sự kiện cấp bộ)
id, uuidnumber, stringSố nội bộ và mã định danh của file.
code, externalCodestring | nullMã hệ thống và mã của bạn.
fileNamestring | nullTên file.
documentTypenumber | nullKhối tài liệu, xem bảng khối.
sizenumber | nullDung lượng (byte).
status, statusLabelnumber, stringTrạng thái file: 1 Chờ sử dụng, 32 Đang dùng, 33 Hoàn thành, 34 Bị từ chối, 35 Ký bị từ chối.
signUrlstring | nullLiên kết tải file (bản mới nhất), sống mặc định 7 ngày. Có ở mọi sự kiện cấp file khi file có nội dung. Nên tải sớm.
Các trường còn lại trong data
occurredAtstringLúc sự kiện xảy ra (UTC).
actorobject | null{ id, name } của người thao tác. Chỉ có ở sự kiện từ chối.
reasonstring | nullLý do. Có ở shared_file.rejected, shared_file.sign_rejected, shared_set.rejected.
dossierobject | null{ id, code } của hồ sơ bị từ chối ký. Chỉ có ở shared_file.sign_rejected.
commentobject | nullChỉ có ở shared.comment_created. Gồm id, content (Markdown), contentHtml, createdAt, author (type, id, name). Giống đối tượng comment, riêng author có thêm id.

Ví dụ từng sự kiện

Mỗi ví dụ chỉ hiện type và data, đã rút gọn. Các khóa không nêu vẫn có trong gói tin với giá trị null. Phần phong bì (id, createdAt, metadata) như mục trên.

shared_set.createdBộ mới tạo
{
  "type": "shared_set.created",
  "data": {
    "sharedSet": { "id": 1001, "uuid": "3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34", "code": "NS-26-000042", "externalCode": "HD-2026-001", "customerName": "Nguyễn Văn A", "status": 30, "statusLabel": "Đã phát hành" },
    "file": null,
    "occurredAt": "2026-10-12T08:00:00.000Z"
  }
}
shared_file.createdFile được thêm vào bộ
{
  "type": "shared_file.created",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "status": 30, "statusLabel": "Đã phát hành" },
    "file": { "id": 2001, "uuid": "9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12", "code": "NS-26-000042.1", "externalCode": "FILE-A", "fileName": "cccd.pdf", "documentType": 13, "size": 12345, "status": 1, "statusLabel": "Chờ sử dụng", "signUrl": "https://storage.example/presigned-link?X-Amz-Signature=..." },
    "occurredAt": "2026-10-12T08:00:00.000Z"
  }
}
shared_file.cert_statement_uploadedPhòng công chứng gửi lời chứng

Mỗi file lời chứng một sự kiện. Nếu gửi nhiều file, gom theo data.sharedSet.uuid. Tải thẳng bằng data.file.signUrl, không cần gọi thêm API.

{
  "type": "shared_file.cert_statement_uploaded",
  "data": {
    "sharedSet": { "id": 1001, "uuid": "3f2b8c1e-7a4d-4e5f-9b21-0c6d8e1a2f34", "code": "NS-26-000042", "externalCode": "HD-2026-001", "status": 30, "statusLabel": "Đã phát hành" },
    "file": { "id": 2010, "uuid": "e91a6b24-3c58-4d7f-b0a2-6c4f8d1e7a39", "fileName": "loi-chung.pdf", "documentType": 19, "size": 51200, "status": 1, "statusLabel": "Đã gửi", "signUrl": "https://storage.example/presigned-link?X-Amz-Signature=..." },
    "occurredAt": "2026-10-12T08:30:00.000Z"
  }
}
shared_file.usedFile được kéo vào hồ sơ
{
  "type": "shared_file.used",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "status": 30, "statusLabel": "Đã phát hành" },
    "file": { "id": 2001, "code": "NS-26-000042.1", "fileName": "cccd.pdf", "documentType": 13, "status": 32, "statusLabel": "Đang dùng" },
    "occurredAt": "2026-10-12T09:00:00.000Z"
  }
}
shared_file.completedKý xong, trả kết quả

Tải bản đã ký ngay bằng data.file.signUrl, hoặc dùng API tải file với data.file.uuid.

{
  "type": "shared_file.completed",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "status": 30, "statusLabel": "Đã phát hành" },
    "file": { "id": 2001, "uuid": "9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12", "code": "NS-26-000042.1", "fileName": "cccd.pdf", "documentType": 14, "status": 33, "statusLabel": "Hoàn thành", "signUrl": "https://storage.example/presigned-link?X-Amz-Signature=..." },
    "occurredAt": "2026-10-13T14:25:30.000Z"
  }
}
shared_file.rejectedPhòng công chứng từ chối một file
{
  "type": "shared_file.rejected",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "status": 30, "statusLabel": "Đã phát hành" },
    "file": { "id": 2001, "code": "NS-26-000042.1", "fileName": "cccd.pdf", "status": 34, "statusLabel": "Bị từ chối" },
    "occurredAt": "2026-10-12T16:00:00.000Z",
    "actor": { "id": 123, "name": "Phạm Văn X" },
    "reason": "CCCD hết hạn"
  }
}
shared_file.sign_rejectedHồ sơ ký bị từ chối, file quay lại
{
  "type": "shared_file.sign_rejected",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "status": 30, "statusLabel": "Đã phát hành" },
    "file": { "id": 2001, "code": "NS-26-000042.1", "fileName": "cccd.pdf", "status": 35, "statusLabel": "Ký bị từ chối" },
    "occurredAt": "2026-10-13T10:15:00.000Z",
    "actor": { "id": 456, "name": "Trần Thị Y" },
    "reason": "Chữ ký chưa rõ",
    "dossier": { "id": 5000, "code": "HS-2026-99999" }
  }
}
shared_set.rejectedPhòng công chứng từ chối cả bộ
{
  "type": "shared_set.rejected",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "customerName": "Nguyễn Văn A", "status": 31, "statusLabel": "Từ chối" },
    "file": null,
    "occurredAt": "2026-10-12T17:30:00.000Z",
    "actor": { "id": 123, "name": "Phạm Văn X" },
    "reason": "Không đủ điều kiện công chứng"
  }
}
shared.comment_created (cấp bộ)Trao đổi mới trên cả bộ

data.file là null. Trao đổi do cán bộ Phòng công chứng viết (author.type là ORG).

{
  "type": "shared.comment_created",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "status": 30, "statusLabel": "Đã phát hành" },
    "file": null,
    "occurredAt": "2026-10-12T07:30:00.000Z",
    "comment": {
      "id": 8891,
      "content": "Nhờ bổ sung **CCCD mặt sau**.",
      "contentHtml": "<p>Nhờ bổ sung <strong>CCCD mặt sau</strong>.</p>",
      "createdAt": "2026-10-12T07:30:00.000Z",
      "author": { "type": "ORG", "id": 45, "name": "Nguyễn Văn A" }
    }
  }
}
shared.comment_created (cấp file)Trao đổi mới trên một file

data.file là một object. Trao đổi này do chính bạn gửi qua API (author.type là API), nên hệ thống tự động của bạn cần bỏ qua.

{
  "type": "shared.comment_created",
  "data": {
    "sharedSet": { "id": 1001, "code": "NS-26-000042", "status": 30, "statusLabel": "Đã phát hành" },
    "file": { "id": 2001, "uuid": "9d1e5c70-2b3a-4c88-a6f1-5e7b0d4c9a12", "code": "NS-26-000042.1", "fileName": "cccd.pdf", "documentType": 13, "status": 1, "statusLabel": "Chờ sử dụng" },
    "occurredAt": "2026-10-12T08:05:00.000Z",
    "comment": {
      "id": 8893,
      "content": "File này gửi nhầm bản cũ, nhờ phòng **bỏ qua**.",
      "contentHtml": "<p>File này gửi nhầm bản cũ, nhờ phòng <strong>bỏ qua</strong>.</p>",
      "createdAt": "2026-10-12T08:05:00.000Z",
      "author": { "type": "API", "id": 77, "name": "Đối tác A" }
    }
  }
}

Gửi lại, quá giờ và chống trùng

Cơ chếHành vi
Thế nào là thành côngĐịa chỉ của bạn trả mã 2xx. Mã khác (kể cả 3xx), quá giờ hoặc lỗi kết nối đều tính là thất bại.
Thời gian chờ30 giây mỗi lần gửi.
Số lần thửTối đa 6 lần (gồm lần đầu).
Lịch gửi lạiNgay → 1 giây → 10 giây → 60 giây → 10 phút → 1 giờ.
Chống trùngMỗi lần gửi lại giữ nguyên X-Webhook-Id. Hãy lưu mã đã xử lý và bỏ qua mã gặp lại.
Hết lượtThử đủ 6 lần vẫn thất bại thì đánh dấu EXHAUSTED. Quản trị viên eNotary xem được nhật ký và gửi lại thủ công.
Tự tắt khi lỗi kéo dàiNếu có 20 sự kiện liên tiếp hết lượt mà chưa lần nào thành công, webhook của bạn bị tự tắt để tránh gửi vô ích. Một lần thành công sẽ đặt lại bộ đếm. Muốn bật lại, nhờ quản trị viên eNotary.

Thời gian chờ, số lần thử và hạn của liên kết tải là giá trị mặc định, có thể được cấu hình theo từng môi trường.

Khuyến nghị

  • Luôn kiểm tra chữ ký trước khi làm bất cứ việc gì khác.
  • Trả 2xx thật nhanh. Tải file, ghi cơ sở dữ liệu… hãy đẩy sang tiến trình nền để khỏi quá 30 giây và bị gửi lại.
  • Chống trùng theo X-Webhook-Id hoặc id trong body.
  • Tải file sớm. signUrl hết hạn sau 7 ngày (mặc định). Cần lâu hơn thì dùng API tải file.
  • Đối chiếu khi quan trọng. Với quyết định lớn, gọi chi tiết bộ để xác nhận trạng thái hiện tại.
  • Giữ kín secret như mật khẩu. Nghi bị lộ thì nhờ eNotary đổi secret.
  • Chọn đúng sự kiện. Chỉ đăng ký những sự kiện bạn thật sự xử lý.

8Trạng thái

REST API trả trạng thái bộ dưới dạng chữ. Webhook trả dạng số kèm nhãn tiếng Việt.

Trạng thái của bộ

partnerStatus (REST)status (webhook)statusLabelÝ nghĩa
DRAFT1NhápĐang tạo, Phòng công chứng chưa thấy. Bộ tạo qua API chuyển sang READY ngay.
READY30Đã phát hànhPhòng công chứng thấy và dùng được.
REJECTED31Từ chốiPhòng công chứng từ chối cả bộ. Trạng thái cuối. Không thêm file được nữa.
Trạng thái của bộNháp chuyển sang Đã phát hành, rồi có thể chuyển sang Từ chối. Nháp (1)DRAFT Đã phát hành (30)READY Từ chối (31)REJECTED tạo xong shared_set.rejected

Trạng thái của file

statusstatusLabelÝ nghĩaWebhook
1Chờ sử dụngMới nhận, chưa dùng.shared_file.created
32Đang dùngĐã kéo vào một hồ sơ.shared_file.used
33Hoàn thànhĐã ký xong và trả kết quả. documentType chuyển thành 14.shared_file.completed
34Bị từ chốiPhòng công chứng từ chối riêng file này. Trạng thái cuối.shared_file.rejected
35Ký bị từ chốiHồ sơ ký bị hủy giữa chừng. File được chọn lại.shared_file.sign_rejected

Lời chứng (documentType 19) không đi qua các bước trên. Nhãn của nó luôn là "Đã gửi".

Sơ đồ trạng thái của fileChờ sử dụng chuyển sang Đang dùng rồi Hoàn thành. Có nhánh Bị từ chối và Ký bị từ chối. Chờ sử dụng (1)vừa nhận từ bạn Đang dùng (32)đã vào hồ sơ Hoàn thành (33)có bản đã ký Bị từ chối (34)trạng thái cuối Ký bị từ chối (35)chọn lại được kéo vào hồ sơshared_file.used ký xongshared_file.completed Phòng CC từ chối fileshared_file.rejected hồ sơ ký bị từ chốishared_file.sign_rejected chọn lại vào hồ sơ
Từ chối file chỉ làm được khi file còn "Chờ sử dụng". Từ chối cả bộ chỉ làm được khi bộ đã phát hành.
Biết file đã xong bằng cách nào?Một trong ba dấu hiệu: webhook shared_file.completed, hoặc status = 33, hoặc documentType = 14 (section = COMPLETED) khi bạn gọi chi tiết file. Nhớ rằng signStatus của bộ hiện chưa phản ánh việc ký.

9Mã lỗi

Nội dung lỗi (message) là chữ gốc từ hệ thống nên có thể là tiếng Anh hoặc tiếng Việt. Hãy dựa vào mã HTTP trước.

HTTPmessageÝ nghĩaCách xử lý
Xác thực
401Missing X-API-Key headerYêu cầu không có header khóa.Thêm header X-API-Key.
401Invalid or revoked API keyKhóa sai hoặc đã bị thu hồi.Kiểm tra lại khóa. Xin cấp khóa mới nếu cần.
401API key has expiredKhóa đã hết hạn.Xin eNotary cấp khóa mới.
Quyền
403This API key must be bound to a partner account to create shared documentsKhóa không gắn tài khoản đối tác, nên không tạo được bộ.Nhờ eNotary gắn khóa với tài khoản đối tác.
403This API key must be bound to a partner account to append filesNhư trên, nhưng cho việc thêm file.Như trên.
403Access deniedBộ hoặc file thuộc Phòng công chứng khác (khi tải file hoặc trao đổi).Kiểm tra lại uuid và khóa bạn đang dùng.
Dữ liệu gửi lên
400File validation failed kèm mảng errorsCó file lỗi. Không có gì được tạo.Đọc errors[] (có index, fileName, error), sửa file lỗi rồi gửi lại. Xem bảng bên dưới.
400Mảng chữ, ví dụ code must be a string, files must contain no more than 20 elementsMột hoặc nhiều trường sai kiểu, thiếu hoặc quá giới hạn.Đọc từng dòng trong message và sửa trường tương ứng.
400A shared set may contain at most 20 filesQuá 20 file.Chia thành nhiều lần: tạo bộ rồi thêm file.
400Invalid ecmClassId: … / Invalid ecmClassCode: …Loại bộ không hợp lệ.Dùng 21, 22, shared-notary-set hoặc shared-cert-set.
400Provide one of: id, uuid, codeChi tiết bộ thiếu tham số tìm.Thêm một trong id, uuid, code.
400Provide exactly one selector: uuid | code | externalCodeChi tiết file thiếu tham số tìm.Thêm một trong ba tham số.
400fileType must be one of: ORIGINAL, SIGNED, MERGED hoặc …one of the following values…fileType sai khi thêm file.Dùng đúng một trong ba giá trị.
400At least one file is requiredThêm file bằng multipart mà không có file.Gửi ít nhất một file trong trường files.
400Route này chỉ nhận: pdf, Định dạng .xxx không được phép, File rỗng hoặc không hợp lệUpload file không phải PDF hoặc file rỗng.Chỉ gửi file PDF thật, có nội dung.
400content must not be empty, Nội dung trao đổi không được để trống, Nội dung trao đổi tối đa 10000 ký tựTrao đổi rỗng hoặc quá dài.Viết 1 đến 10.000 ký tự.
404Shared set not foundKhông thấy bộ, hoặc bộ thuộc đơn vị khác. Thường do dùng nhầm uuid file.Dùng uuid của bộ. Kiểm tra lại bằng chi tiết bộ.
404File not found, File content not available, File is not part of a shared setKhông thấy file, file chưa có nội dung, hoặc file không thuộc bộ chia sẻ nào.Kiểm tra uuid file.
409A shared set with code "…" already exists for this organization kèm existingIdMã code đã có.Dùng mã khác, hoặc lấy bộ cũ bằng chi tiết bộ ?code=.
409Shared set was rejected — cannot append filesBộ đã bị từ chối.Tạo bộ mới với mã khác.
413Kích thước file vượt quá giới hạn cho phépUpload có file quá 150 MB.Giảm dung lượng file.
Giới hạn và hệ thống
405Trang HTML 405 Not Allowed (nginx), không phải JSONGọi nhầm vào địa chỉ trang web (ví dụ admin-dev.enotaryid.com), yêu cầu không tới API.Đổi sang base URL https://api-dev.enotaryid.com/api/integration (Dev) hoặc https://api.enotaryid.com/api/integration (Production).
429Rate limit exceeded (60 req/min) hoặc (600 req/min)Gọi quá nhanh.Chờ rồi gọi lại, tăng dần thời gian chờ.
429Đã sử dụng hết dung lượng lưu trữ…Phòng công chứng đã hết dung lượng lưu trữ (gặp khi thêm file).Liên hệ eNotary để được hỗ trợ. Chờ không giúp được.
5xxtùy lỗiLỗi phía eNotary.Thử lại sau ít phút. Nếu kéo dài, báo cho eNotary kèm thời điểm và mã yêu cầu.

Lỗi chi tiết của từng file (errors[].error)

{
  "message": "File validation failed",
  "errors": [
    { "index": 1, "fileName": "hop-dong.pdf", "error": "File is not a valid PDF (missing %PDF- header)" }
  ]
}
errorÝ nghĩa
File is not a valid PDF (missing %PDF- header)Nội dung không phải PDF.
File exceeds 150 MBFile quá 150 MB.
Total set exceeds 300 MBTổng các file trong một lần gửi quá 300 MB.
Only application/pdf is accepted, got …mimeType khai báo không phải PDF.
Empty base64 payload, Missing base64Data, Missing urlThiếu nội dung file.
SSRF guard: only https URLs allowed, got …URL không phải https.
SSRF guard: … (địa chỉ nội bộ, không phân giải được…)URL trỏ vào địa chỉ nội bộ hoặc không truy cập được.
Unexpected content-type: … (expected application/pdf)Máy chủ chứa file trả về kiểu không phải PDF.
Fetch failed: …, HTTP 404, Too many redirectsKhông tải được file từ URL (lỗi mạng, hết hạn, quá 15 giây hoặc quá 3 lần chuyển hướng).

10Câu hỏi thường gặp

Tôi nhận lỗi 404 "Shared set not found" khi thêm file. Sai ở đâu?

Rất có thể bạn dùng uuid của file thay vì uuid của bộ. Uuid của bộ nằm ngoài cùng trong phản hồi tạo bộ (data.uuid), còn uuid của file nằm trong data.files[].

Tôi có thể tạo bộ rỗng rồi thêm file sau không?

Có. Bỏ trống files khi tạo, sau đó gọi thêm file với fileType: "ORIGINAL".

Tôi gửi lại yêu cầu tạo bộ sau khi bị đứt mạng. Có bị tạo trùng không?

Không. Mã code phải duy nhất trong Phòng công chứng, nên lần gửi lại nhận 409 kèm existingId. Bạn cũng có thể gọi chi tiết bộ với ?code= trước để biết bộ đã có chưa.

Tôi có gửi được file Word hoặc ảnh không?

Không. Khi tạo bộ hoặc thêm file, chỉ nhận PDF. Hãy đổi sang PDF trước khi gửi.

Làm sao biết một file đã được ký xong?

Nhận webhook shared_file.completed, hoặc gọi chi tiết file và xem status = 33 (hoặc documentType = 14). Trường signStatus của bộ hiện luôn là NOT_SIGNED, đừng dùng nó.

Sao tôi nhận cùng một webhook nhiều lần?

Khi địa chỉ của bạn không trả 2xx kịp, eNotary gửi lại với cùng X-Webhook-Id. Hãy lưu mã đã xử lý và bỏ qua mã trùng. Xem Gửi lại và chống trùng.

Webhook của tôi đột nhiên không đến nữa?

Nếu địa chỉ của bạn lỗi liên tục (20 sự kiện liên tiếp hết lượt gửi), hệ thống tự tắt webhook. Sửa lỗi phía bạn rồi nhờ quản trị viên eNotary bật lại. Cũng kiểm tra địa chỉ có là HTTPS công khai, và không chuyển hướng.

Hệ thống của tôi bị lặp vô hạn khi dùng trao đổi?

Webhook shared.comment_created cũng bắn cho trao đổi do chính bạn gửi qua API. Hãy bỏ qua sự kiện có data.comment.author.type === "API".

Liên kết signUrl hết hạn thì sao?

Liên kết sống mặc định 7 ngày. Hết hạn, bạn vẫn tải được bằng API tải file (dùng uuid của file và X-API-Key), hoặc gọi lại chi tiết bộ/file để nhận liên kết mới.

Tôi bị lỗi 429. Phải làm gì?

Bạn gọi quá 60 yêu cầu mỗi phút cho một khóa (hoặc 600 cho cả đơn vị). Chờ rồi gọi lại, tăng dần thời gian chờ. Nếu message nói về dung lượng lưu trữ thì đó là hết dung lượng, hãy liên hệ eNotary.

Một file có dùng được cho nhiều hồ sơ không?

Không. Khi đã "Đang dùng" trong một hồ sơ, file không chọn được cho hồ sơ khác. Cần cùng một văn bản cho nhiều hồ sơ thì gửi nhiều file.

Tôi cần giới hạn tần suất cao hơn hoặc thêm khóa API?

Liên hệ đầu mối kỹ thuật eNotary được cung cấp khi onboarding.

11Sandbox (chỉ Dev)

Tự đóng vai Phòng công chứng để test đủ trạng thái và webhook, không cần chờ ai thao tác.

Cần biết Chỉ chạy ở api-dev.enotaryid.com, Production trả 404. Trạng thái và webhook là thật. File "hoàn thành" chỉ là bản gốc đóng dấu SANDBOX, không có chữ ký số.

Cách test trong 4 bước

  1. Tạo bộ như bình thường bằng 4.1. Ghi lại uuid của bộ và của file.
  2. Đóng vai Phòng công chứng: gọi API giả lập với sự kiện cần test, ví dụ từ chối file:
    curl -X POST "https://api-dev.enotaryid.com/api/integration/sandbox/documents/{uuid}/simulate" \
      -H "X-API-Key: enk_xxxxxxxx" -H "Content-Type: application/json" \
      -d '{ "event": "shared_file.rejected", "fileUuid": "{fileUuid}" }'
  3. Kiểm tra hệ thống của bạn: nhận được webhook shared_file.rejected, và gọi chi tiết file thấy status = 34.
  4. Lặp lại với sự kiện khác (gửi bản mới, trao đổi, shared_file.used rồi shared_file.completed…). Muốn test chống trùng thì gửi lại webhook.

Lệnh cho từng sự kiện: xem lệnh mẫu. Không viết code cũng test được: dùng nút trên giao diện web.

11.1 Giả lập một sự kiện

POST/api/integration/sandbox/documents/{uuid}/simulate

uuid là uuid của bộ. Body:

TênBắt buộcMô tả
eventCóTên sự kiện, xem bảng dưới.
fileUuidTuỳuuid của file, cần cho sự kiện cấp file.
reason / contentKhôngLý do từ chối / nội dung trao đổi.
delaySecondsKhôngGửi webhook trễ 0–300 giây. Trạng thái vẫn đổi ngay.

Các sự kiện

eventLàm được khiKết quả
shared_file.usedFile "Chờ sử dụng" hoặc "Ký bị từ chối"Đang dùng (32)
shared_file.completedFile "Đang dùng"Hoàn thành (33)
shared_file.sign_rejectedFile "Đang dùng"Ký bị từ chối (35)
shared_file.rejectedFile gốc "Chờ sử dụng"Bị từ chối (34)
shared_set.rejectedBộ READYBộ bị từ chối (31)
shared_file.cert_statement_uploadedBộ READYThêm 1 file lời chứng
shared.comment_createdBộ chưa bị từ chốiThêm 1 trao đổi của Phòng công chứng

Gọi sai trạng thái trả 409, không đổi gì và không có webhook. Muốn chạy lại từ đầu thì tạo bộ mới. Ví dụ kịch bản đầy đủ: làm lần lượt theo hình ở mục 1.

Lệnh mẫu (copy là chạy)

Điền 4 biến đầu một lần, sau đó chạy lệnh của sự kiện cần test. SET là uuid của bộ, FILE là uuid của file.

BASE="https://api-dev.enotaryid.com/api/integration"
KEY="enk_xxxxxxxx"      # khoá API Dev của bạn
SET="uuid-cua-bo"
FILE="uuid-cua-file"

# Kéo file vào hồ sơ → shared_file.used
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared_file.used","fileUuid":"'$FILE'"}'

# Ký xong → shared_file.completed (file phải đang "Đang dùng")
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared_file.completed","fileUuid":"'$FILE'"}'

# Hồ sơ ký bị từ chối → shared_file.sign_rejected (file phải đang "Đang dùng")
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared_file.sign_rejected","fileUuid":"'$FILE'","reason":"Thiếu chữ ký"}'

# Phòng công chứng từ chối file → shared_file.rejected
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared_file.rejected","fileUuid":"'$FILE'","reason":"Thiếu trang 2"}'

# Phòng công chứng từ chối cả bộ → shared_set.rejected
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared_set.rejected","reason":"Sai đơn vị"}'

# Phòng công chứng gửi lời chứng → shared_file.cert_statement_uploaded
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared_file.cert_statement_uploaded"}'

# Phòng công chứng nhắn tin → shared.comment_created
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared.comment_created","content":"Nhờ bổ sung **CCCD**"}'

# Bất kỳ sự kiện nào, webhook đến trễ 60 giây: thêm "delaySeconds":60
curl -X POST "$BASE/sandbox/documents/$SET/simulate" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"event":"shared_file.used","fileUuid":"'$FILE'","delaySeconds":60}'

# Xem webhook đã gửi của bộ
curl "$BASE/sandbox/documents/$SET/webhooks" -H "X-API-Key: $KEY"

# Gửi lại một webhook (EVT = trường "id" của webhook đã nhận)
curl -X POST "$BASE/sandbox/webhooks/$EVT/resend" -H "X-API-Key: $KEY"

11.2 Gửi lại một webhook

POST/api/integration/sandbox/webhooks/{eventId}/resend

Gửi lại đúng gói tin cũ, cùng id, để test chống trùng. eventId là trường id của webhook bạn đã nhận.

Xem các webhook đã gửi của một bộ: GET /api/integration/sandbox/documents/{uuid}/webhooks.

11.3 Test bằng giao diện web

Không cần viết code: đăng nhập web Dev, làm 4 bước dưới đây. Ảnh chụp từ môi trường Dev thật.

① Mở bộ tài liệu, bấm nút viền cam Giả lập sự kiện (DEV).
Màn chi tiết bộ tài liệu có nút Giả lập sự kiện (DEV)
② Chọn sự kiện, chọn tài liệu, nhập lý do (nếu có) rồi bấm Giả lập. Danh sách chỉ hiện sự kiện còn làm được.
Hộp thoại chọn sự kiện từ chối file
③ Trạng thái đổi ngay trên màn hình (ví dụ "Bị từ chối" kèm lý do). Cùng lúc hệ thống của bạn nhận webhook.
File chuyển sang Bị từ chối
④ Mở lại hộp thoại để xem webhook đã gửi. Bấm Gửi lại để test chống trùng (cùng id).
Bảng webhook đã gửi kèm nút Gửi lại

Thay đổi gần đây

22/10/2026  Sandbox trên Dev

  • Mới mục 11: tự giả lập thao tác của Phòng công chứng và gửi lại webhook trên Dev. API Production không đổi.

22/10/2026  Thêm kịch bản từ chối, gửi lại và trao đổi

  • Thêm hình kịch bản đầy đủ ở mục 1: Phòng công chứng từ chối file, bạn gửi bản mới vào cùng bộ, hai bên trao đổi, rồi công chứng và ký. Mỗi bước ghi rõ API gọi và webhook nhận.
  • Ghi rõ: bản gửi lại là file mới (uuid mới), từ chối cả bộ thì phải tạo bộ mới, phát hiện sai sau khi file đã vào hồ sơ thì nhận shared_file.sign_rejected.
  • API không thay đổi.

21/10/2026  Thêm hướng dẫn tránh lỗi thường gặp

  • Nhấn mạnh không gọi API qua địa chỉ trang web quản trị (admin-dev…): sẽ nhận 405 Not Allowed. Thêm dòng 405 vào bảng mã lỗi.
  • Thêm hướng dẫn điền form-data trong Postman cho API tạo bộ bằng multipart: key files, titles dạng mảng JSON, bắt buộc code.
  • API không thay đổi.

12/10/2026  Trao đổi chuyển sang Markdown

  • Gửi trao đổi: body { "content": "…" } nay nhận Markdown (tối đa 10.000 ký tự). Khi hiển thị chỉ giữ thẻ định dạng an toàn; script, ảnh và style bị loại bỏ.
  • Ý nghĩa của content đã đổi: trước đây là HTML đã escape. Nay là Markdown gốc. Trao đổi cũ (viết trước ngày này) giữ nguyên HTML đã lưu.
  • Trường mới trong API trao đổi và webhook shared.comment_created (data.comment): contentHtml (HTML đã lọc, hiển thị thẳng được, áp dụng cho cả trao đổi cũ lẫn mới).
  • Khi bạn gửi trao đổi, cán bộ Phòng công chứng xử lý bộ nhận thông báo trong ứng dụng. Bạn không cần làm gì thêm.
  • Việc cần làm: nếu bạn từng hiển thị content như HTML, hãy chuyển sang dùng contentHtml.

12/10/2026  Viết lại tài liệu và đính chính

  • Tài liệu được viết lại: thêm địa chỉ Dev/Production, mục lục, sơ đồ và ví dụ cho từng API.
  • Đính chính: partnerStatus là chữ (DRAFT, READY, REJECTED), không phải số. Danh sách bộ dùng pageSize (không phải limit). Giới hạn body JSON là 150 MB. signStatus hiện luôn là NOT_SIGNED. Phản hồi thêm file nằm trong phong bì (data là mảng). signUrl trong webhook có ở mọi sự kiện cấp file.
  • Bổ sung: webhook tự tắt sau 20 sự kiện liên tiếp hết lượt gửi.