Webhook

Nhận sự kiện realtime từ NextX bằng HTTP push thay vì poll. Mỗi sự kiện gửi POST JSON tới URL của bạn, ký HMAC-SHA256 để bạn xác thực nguồn gốc.

1. Tổng quan

Webhook hoạt động như của Meta: mỗi workspace có đúng một webhook (một URL) cho mỗi dịch vụ — CRM, Sales, Tài khoản & Chi nhánh, Office, Chat đa kênh. Bạn chọn dịch vụ, gắn một URL HTTPS, rồi bật từng sự kiện của chính dịch vụ đó. Khi sự kiện xảy ra trong workspace, NextX gửi request có signature tới URL. Endpoint phải trả 2xx trong vòng 15 giây; nếu không, hệ thống giao lại — tối đa 8 lần giao (lần đầu + 7 lần retry, trải ~24 giờ), giãn cách tăng dần (xem mục Giao & retry).

Webhook được quản lý ở cấp workspace bằng session người dùng: Authorization: Bearer <USER_ACCESS_TOKEN> của người có quyền webhook.endpoints.manage (xem danh sách cần webhook.endpoints.view, nhật ký giao cần webhook.deliveries.view / webhook.deliveries.manage). Token OAuth của ứng dụng (client_credentials) và API key không gọi được các API này: module webhook không được cấp cho ứng dụng. App/agent có thể hướng dẫn khách, hoặc khách tự cấu hình trong app.

2. Cấu hình webhook theo dịch vụ

Trong app: Thiết lập › Ứng dụng & API › Webhooks → chọn dịch vụ → dán URL → Xác minh và lưu → bật từng sự kiện cần nhận. Các bước dưới đây là cùng luồng đó qua API.

Bước 1 — Liệt kê dịch vụ và sự kiện

curl https://api.nextx.vn/api/v1/webhook/services \
  -H "Authorization: Bearer <USER_ACCESS_TOKEN>"

Trả về services[]: mỗi dịch vụ có mã service (CRM, SALES, IAM, OFFICE, COMM), displayName, danh sách events[] (eventType, displayName, description) và endpoint — webhook đang có, hoặc null nếu chưa cấu hình. legacy[] liệt kê endpoint kiểu cũ (tạo không kèm dịch vụ) chưa xoá.

Bước 2 — Xác minh URL (nên làm)

curl -X POST https://api.nextx.vn/api/v1/webhook/endpoints/verify \
  -H "Authorization: Bearer <USER_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-app.com/webhooks/nextx/crm", "verifyToken": "my-random-token" }'

NextX gửi một POST có type: "endpoint.verification", chuỗi ngẫu nhiên challenge và verify_token của bạn. Máy chủ của bạn phải trả 2xx và có chuỗi challenge ở đâu đó trong body phản hồi. Lệnh này không lưu gì; kết quả gồm success, responseStatus, durationMs, challenge, responseBody, errorMessage và failureReason (0 đạt · 1 không gọi tới được — DNS/TLS/quá hạn/IP nội bộ · 2 máy chủ trả error code · 3 trả 2xx nhưng không trả lại challenge).

// Node.js (Express) — answer the verification ping BEFORE checking the signature
app.post("/webhooks/nextx/crm", express.raw({ type: "application/json" }), (req, res) => {
  const event = JSON.parse(req.body.toString("utf8"));
  if (event.type === "endpoint.verification") {
    // optional: compare event.verify_token with the verifyToken you sent
    return res.status(200).json({ challenge: event.challenge });
  }
  // ...verify X-NextX-Signature-256 (section 6), then process the event
  res.sendStatus(200);
});

Gói xác minh được ký bằng secret dùng một lần (lúc này chưa có secret thật), nên nó sẽ không qua bước verify signature của bạn — hãy xử lý endpoint.verification trước, như ví dụ trên. Trả lại challenge không làm lộ gì secret.

Bước 3 — Đặt URL cho dịch vụ (tạo hoặc sửa)

curl -X PUT https://api.nextx.vn/api/v1/webhook/services/CRM \
  -H "Authorization: Bearer <USER_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/nextx/crm",
    "subscribedEvents": ["contact.created", "contact.updated"]
  }'

PUT /api/v1/webhook/services/{service} với body { url, subscribedEvents?, customHeaders? }. Lần gọi đầu tạo webhook (201, created: true) và trả signingSecret (whk_sec_…) duy nhất lần này — lưu kỹ để verify signature:

HTTP/1.1 201 Created
{
  "id": "<endpoint_id>",
  "service": "CRM",
  "url": "https://your-app.com/webhooks/nextx/crm",
  "status": "Active",
  "subscribedEvents": ["contact.created", "contact.updated"],
  "created": true,
  "signingSecret": "whk_sec_3f9a...c21",
  "createdAt": "2026-10-08T03:12:56Z",
  "updatedAt": "2026-10-08T03:12:56Z"
}

Gọi lại là sửa URL (200, created: false, không trả secret). Khi sửa, bỏ trống subscribedEvents / customHeaders = giữ nguyên; khi tạo, danh sách rỗng hoặc bỏ trống = chưa nhận sự kiện nào — webhook im lặng cho tới khi bạn bật sự kiện.

Bước 4 — Bật/tắt từng sự kiện, hoặc xoá webhook

# Turn one event on (or off with "subscribed": false)
curl -X PUT https://api.nextx.vn/api/v1/webhook/services/CRM/events/deal.won \
  -H "Authorization: Bearer <USER_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{ "subscribed": true }'

# Remove the service's webhook (it can be created again right away with PUT)
curl -X DELETE https://api.nextx.vn/api/v1/webhook/services/CRM \
  -H "Authorization: Bearer <USER_ACCESS_TOKEN>"
  • Mã dịch vụ viết HOA và so khớp chính xác (CRM, không phải crm). Mã không có sự kiện nào đang chào bán → 404 webhook.service.not_found.
  • Sự kiện thuộc dịch vụ khác → 422 webhook.event.not_in_service (vd order.completed trên CRM); sự kiện không tồn tại → cũng 422.
  • Bật/tắt sự kiện hoặc xoá khi dịch vụ chưa có webhook → 404 webhook.service.not_configured.
  • Đường cũ POST /api/v1/webhook/endpoints vẫn chạy nhưng nay bắt buộc service; dịch vụ đã có webhook → 409 webhook.endpoint.service_exists. Body trộn sự kiện CRM với Sales mà không có service sẽ bị từ chối — hãy dùng mỗi dịch vụ một lệnh PUT services/{service}.

Các thao tác theo id vẫn áp dụng cho webhook của dịch vụ (id nằm ở endpoint.id): GET /endpoints/{id}, POST /endpoints/{id}/disable / /enable, POST /endpoints/{id}/rotate-secret, POST /endpoints/{id}/test, và nhật ký giao tại /endpoints/{id}/deliveries.

3. Danh mục sự kiện

33 sự kiện thuộc 8 dịch vụ. Cột Nguồn là mã dịch vụ — webhook của dịch vụ nào chỉ bật được sự kiện của dịch vụ đó.

CRM · CRMSALES · SalesIAM · Tài khoản & Chi nhánhOFFICE · OfficeCOMM · Chat đa kênhHRM · Nhân sựPAYMENT · Thanh toánEINVOICE · Hoá đơn điện tử
contact.identifiedCOMMPROKhách vừa nhắn số điện thoại trong chat. Kèm nguồn hội thoại (quảng cáo / UTM) và nhân viên đang phụ trách — đây là mốc ghép hội thoại với đơn hàng ngoài NextX.
TrườngKiểu
data.object.emailstring (email)
data.object.phonestring
data.object.agentIdstring (uuid)
data.object.attributionobject
data.object.channelTypestring
data.object.displayNamestring
data.object.workspaceIdstring (uuid)
data.object.crmContactIdstring (uuid)
data.object.identifiedAtstring (date-time)
data.object.conversationIdstring (uuid)
data.object.channelAccountIdstring (uuid)
data.object.channelContactIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "contact.identified",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "email": "khach@example.com",
      "phone": "string",
      "agentId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "attribution": {
        "adId": "string",
        "adName": null,
        "adsetId": null,
        "utmTerm": null,
        "adsetName": null,
        "utmMedium": null,
        "utmSource": null,
        "adPlatform": "string",
        "landingUrl": null,
        "utmContent": null,
        "referrerUrl": null,
        "utmCampaign": null,
        "adCampaignId": null,
        "adCampaignName": null,
        "attributionMethod": "string"
      },
      "channelType": "string",
      "displayName": "string",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "crmContactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "identifiedAt": "2026-10-27T09:00:00+07:00",
      "conversationId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "channelAccountId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "channelContactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
conversation.attribution_resolvedCOMMPROCây quảng cáo (nhóm QC, chiến dịch) của hội thoại vừa lấy được từ Meta, thường vài phút sau sự kiện trên. Cùng conversationId — cập nhật đè, không phải bản ghi mới.
TrườngKiểu
data.object.resolvedAtstring (date-time)
data.object.attributionobject
data.object.workspaceIdstring (uuid)
data.object.conversationIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "conversation.attribution_resolved",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "resolvedAt": "2026-10-27T09:00:00+07:00",
      "attribution": {
        "adId": "string",
        "adName": "string",
        "adsetId": "string",
        "utmTerm": null,
        "adsetName": "string",
        "utmMedium": null,
        "utmSource": null,
        "adPlatform": "string",
        "landingUrl": null,
        "utmContent": null,
        "referrerUrl": null,
        "utmCampaign": null,
        "adCampaignId": "string",
        "adCampaignName": "string",
        "attributionMethod": "string"
      },
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "conversationId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
contact.createdCRMPROMột liên hệ mới được tạo trong CRM — thủ công, nhập từ file, hoặc tự sinh từ chat/web form.
TrườngKiểu
data.object.codestring
data.object.namestring
data.object.typestring
data.object.emailstring
data.object.phonestring
data.object.sourcestring
data.object.branchIdstring (uuid)
data.object.contactIdstring (uuid)
data.object.createdAtstring (date-time)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "contact.created",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "code": "string",
      "name": "string",
      "type": "string",
      "email": "string",
      "phone": "string",
      "source": "string",
      "branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "createdAt": "2026-10-27T09:00:00+07:00"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
contact.deletedCRMPROLiên hệ bị xoá khỏi CRM.
TrườngKiểu
data.object.contactIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "contact.deleted",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
contact.updatedCRMPROThông tin liên hệ được sửa. Payload là trạng thái SAU khi sửa, không phải phần thay đổi.
TrườngKiểu
data.object.citystring
data.object.codestring
data.object.wardstring
data.object.emailstring
data.object.phonestring
data.object.addressstring
data.object.taxCodestring
data.object.branchIdstring (uuid)
data.object.fullNamestring
data.object.sourceIdstring (uuid)
data.object.contactIdstring (uuid)
data.object.assigneeIdstring (uuid)
data.object.sourceNamestring
data.object.changedFieldsstring[]
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "contact.updated",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "city": "string",
      "code": "string",
      "ward": "string",
      "email": "string",
      "phone": "string",
      "address": "string",
      "taxCode": "string",
      "branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "fullName": "string",
      "sourceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "assigneeId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "sourceName": "string",
      "changedFields": [
        "string"
      ]
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
deal.wonCRMPROCơ hội bán hàng chuyển sang trạng thái thắng. Không bắn cho cơ hội thua hoặc bị huỷ.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "deal.won",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
invoice.cancelledEINVOICEPROHoá đơn điện tử bị huỷ — huỷ trên NextX hoặc đồng bộ thấy đã huỷ ở nhà cung cấp. Payload kèm lý do huỷ.
TrườngKiểu
data.object.statusstring
data.object.cqtCodenull
data.object.issuedAtnull
data.object.invoiceIdstring (uuid)
data.object.referenceIdstring (uuid)
data.object.cancelReasonstring
data.object.errorMessagenull
data.object.invoiceNumbernull
data.object.referenceCodestring
data.object.referenceTypestring
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "invoice.cancelled",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "status": "string",
      "cqtCode": null,
      "issuedAt": null,
      "invoiceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "referenceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "cancelReason": "string",
      "errorMessage": null,
      "invoiceNumber": null,
      "referenceCode": "string",
      "referenceType": "string"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
invoice.failedEINVOICEPROPhát hành hoá đơn điện tử thất bại. Payload gồm chứng từ nguồn và thông báo lỗi nhà cung cấp hoá đơn trả về.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "invoice.failed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
invoice.issuedEINVOICEPROHoá đơn điện tử phát hành thành công. Payload gồm chứng từ nguồn (referenceType / referenceId / referenceCode), số hoá đơn, mã cơ quan thuế (nếu có) và thời điểm phát hành.
TrườngKiểu
data.object.statusstring
data.object.cqtCodestring
data.object.issuedAtstring (date-time)
data.object.invoiceIdstring (uuid)
data.object.referenceIdstring (uuid)
data.object.cancelReasonnull
data.object.errorMessagenull
data.object.invoiceNumberstring
data.object.referenceCodestring
data.object.referenceTypestring
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "invoice.issued",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "status": "string",
      "cqtCode": "string",
      "issuedAt": "2026-10-27T09:00:00+07:00",
      "invoiceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "referenceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "cancelReason": null,
      "errorMessage": null,
      "invoiceNumber": "string",
      "referenceCode": "string",
      "referenceType": "string"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
employee.createdHRMPRONhân viên mới được tạo trong HRM. Payload gồm mã, họ tên, loại nhân viên, ngày vào làm, phòng ban, chức danh và loại hợp đồng đầu tiên.
TrườngKiểu
data.object.codestring
data.object.fullNamestring
data.object.joinDatestring (date)
data.object.orgUnitIdstring (uuid)
data.object.employeeIdstring (uuid)
data.object.positionIdstring (uuid)
data.object.employeeTypestring
data.object.initialContractTypestring
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "employee.created",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "code": "string",
      "fullName": "string",
      "joinDate": "2026-10-27",
      "orgUnitId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "employeeId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "positionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "employeeType": "string",
      "initialContractType": "string"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
employee.terminatedHRMPRONhân viên được cho nghỉ việc. Payload gồm mã, họ tên, ngày nghỉ và số quyết định; không kèm lý do nghỉ.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "employee.terminated",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
employee.updatedHRMPROHồ sơ nhân viên được sửa. Payload là trạng thái SAU khi sửa (mã, họ tên, email, phòng ban, chức danh).

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "employee.updated",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
labor_contract.evaluatedHRMPROHợp đồng lao động được đánh giá. Payload gồm loại hợp đồng, quyết định, tổng điểm / điểm trung bình và hợp đồng kế tiếp nếu có.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "labor_contract.evaluated",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
leave.approvedHRMPROĐơn nghỉ phép được duyệt xong ở bước cuối. Payload gồm nhân viên, loại nghỉ, ký hiệu, từ ngày – đến ngày; không kèm ghi chú duyệt.
TrườngKiểu
data.object.toDatestring (date)
data.object.leaveIdstring (uuid)
data.object.fromDatestring (date)
data.object.leaveTypestring
data.object.approvedAtstring (date-time)
data.object.employeeIdstring (uuid)
data.object.leaveSymbolstring
data.object.workspaceIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "leave.approved",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "toDate": "2026-10-27",
      "leaveId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "fromDate": "2026-10-27",
      "leaveType": "string",
      "approvedAt": "2026-10-27T09:00:00+07:00",
      "employeeId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "leaveSymbol": "string",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
leave.rejectedHRMPROĐơn nghỉ phép bị từ chối. Payload gồm nhân viên, loại nghỉ, từ ngày – đến ngày; không kèm lý do từ chối.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "leave.rejected",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
payroll.lockedHRMPROBảng lương một tháng được chốt. Payload gồm năm, tháng, thời điểm chốt và người chốt; không kèm số tiền.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "payroll.locked",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
payslip.acknowledgedHRMPRONhân viên ký xác nhận phiếu lương. Payload gồm phiếu lương, nhân viên, tháng / năm và thời điểm ký.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "payslip.acknowledged",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
recruitment.hiredHRMPROỨng viên được tuyển. Payload gồm hồ sơ ứng tuyển, ứng viên, tin tuyển dụng, ngày tuyển và nhân viên được tạo (nếu có).

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "recruitment.hired",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
branch.createdIAMPROChi nhánh mới được tạo.
TrườngKiểu
data.object.codestring
data.object.namestring
data.object.addressstring
data.object.branchIdstring (uuid)
data.object.parentIdnull
data.object.managerIdnull
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "branch.created",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "code": "string",
      "name": "string",
      "address": "string",
      "branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "parentId": null,
      "managerId": null
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
branch.updatedIAMPROThông tin chi nhánh được sửa.
TrườngKiểu
data.object.branchIdstring (uuid)
data.object.changedFieldsstring[]
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "branch.updated",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "changedFields": [
        "string"
      ]
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
user.createdIAMPRONgười dùng mới được thêm vào workspace.
TrườngKiểu
data.object.emailstring
data.object.userIdstring (uuid)
data.object.fullNamestring
data.object.createdAtstring (date-time)
data.object.workspaceIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "user.created",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "email": "string",
      "userId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "fullName": "string",
      "createdAt": "2026-10-27T09:00:00+07:00",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
workspace.createdIAMPROWorkspace mới được tạo.
TrườngKiểu
data.object.codestring
data.object.namestring
data.object.localestring
data.object.ownerIdstring (uuid)
data.object.timezonestring
data.object.createdAtstring (date-time)
data.object.ownerEmailstring (email)
data.object.workspaceIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "workspace.created",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "code": "string",
      "name": "string",
      "locale": "string",
      "ownerId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "timezone": "string",
      "createdAt": "2026-10-27T09:00:00+07:00",
      "ownerEmail": "khach@example.com",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
approval.instance.status_changedOFFICEPROPhiếu trình duyệt đổi trạng thái (chờ duyệt / đã duyệt / từ chối / thu hồi).

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "approval.instance.status_changed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
approval.task.completedOFFICEPRONgười duyệt đã xử lý xong bước duyệt của mình.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "approval.task.completed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
approval.task.createdOFFICEPROMột bước duyệt được giao cho người duyệt.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "approval.task.created",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
payment.completedPAYMENTPRONgười mua trả tiền thành công qua cổng thanh toán online (QR, thẻ, ví, trả sau…) hoặc khoản thu tay được xác nhận. Payload gồm mã giao dịch, chứng từ nguồn (referenceType / referenceId — đơn bán, hợp đồng gym…), cổng, số tiền và thời điểm trả.
TrườngKiểu
data.object.amountinteger
data.object.paidAtstring (date-time)
data.object.gatewaystring
data.object.payerIdstring (uuid)
data.object.currencystring
data.object.metadataobject
data.object.netAmountinteger
data.object.payerTypestring
data.object.referenceIdstring (uuid)
data.object.workspaceIdstring (uuid)
data.object.eInvoiceHintnull
data.object.providerCodestring
data.object.referenceTypestring
data.object.transactionIdstring
data.object.dbTransactionIdstring (uuid)
data.object.paymentIntentIdstring (uuid)
data.object.paymentInstrumentstring
data.object.providerApprovalCodestring
data.object.providerTransactionIdstring
data.object.paymentInstrumentDetailstring
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "payment.completed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "amount": 0,
      "paidAt": "2026-10-27T09:00:00+07:00",
      "gateway": "string",
      "payerId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "currency": "string",
      "metadata": {},
      "netAmount": 0,
      "payerType": "string",
      "referenceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "eInvoiceHint": null,
      "providerCode": "string",
      "referenceType": "string",
      "transactionId": "string",
      "dbTransactionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "paymentIntentId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "paymentInstrument": "string",
      "providerApprovalCode": "string",
      "providerTransactionId": "string",
      "paymentInstrumentDetail": "string"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
payment.failedPAYMENTPROLượt thanh toán online thất bại (cổng từ chối, khách huỷ, hết hạn). Payload gồm chứng từ nguồn, cổng và lý do cổng trả về.
TrườngKiểu
data.object.reasonstring
data.object.failedAtstring (date-time)
data.object.referenceIdstring (uuid)
data.object.workspaceIdstring (uuid)
data.object.providerCodestring
data.object.referenceTypestring
data.object.paymentIntentIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "payment.failed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "reason": "string",
      "failedAt": "2026-10-27T09:00:00+07:00",
      "referenceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "providerCode": "string",
      "referenceType": "string",
      "paymentIntentId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
payment.recurring_chargedPAYMENTPROCổng tự trừ thành công một kỳ của hợp đồng thanh toán định kỳ. Payload có số hợp đồng định kỳ và số kỳ (periodNum) — cổng có thể báo lại cùng một kỳ nhiều lần, hãy chống trùng theo cặp này.
TrườngKiểu
data.object.amountinteger
data.object.paidAtstring (date-time)
data.object.payerIdstring (uuid)
data.object.currencystring
data.object.metadataobject
data.object.payerTypestring
data.object.periodNuminteger
data.object.referenceIdstring (uuid)
data.object.workspaceIdstring (uuid)
data.object.providerCodestring
data.object.referenceTypestring
data.object.dbTransactionIdstring (uuid)
data.object.paymentIntentIdstring (uuid)
data.object.paymentInstrumentstring
data.object.recurringContractNostring
data.object.providerApprovalCodenull
data.object.providerTransactionIdstring
data.object.originalPaymentIntentIdstring (uuid)
data.object.paymentInstrumentDetailstring
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "payment.recurring_charged",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "amount": 0,
      "paidAt": "2026-10-27T09:00:00+07:00",
      "payerId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "currency": "string",
      "metadata": {},
      "payerType": "string",
      "periodNum": 0,
      "referenceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "providerCode": "string",
      "referenceType": "string",
      "dbTransactionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "paymentIntentId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "paymentInstrument": "string",
      "recurringContractNo": "string",
      "providerApprovalCode": null,
      "providerTransactionId": "string",
      "originalPaymentIntentId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "paymentInstrumentDetail": "string"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
payment.recurring_failedPAYMENTPROCổng tự trừ một kỳ thanh toán định kỳ nhưng thất bại. Payload gồm số hợp đồng định kỳ, số kỳ, mã đơn của lần trừ và lý do.
TrườngKiểu
data.object.reasonstring
data.object.failedAtstring (date-time)
data.object.periodNuminteger
data.object.referenceIdstring (uuid)
data.object.workspaceIdstring (uuid)
data.object.providerCodestring
data.object.referenceTypestring
data.object.providerOrderRefstring
data.object.recurringContractNostring
data.object.originalPaymentIntentIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "payment.recurring_failed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "reason": "string",
      "failedAt": "2026-10-27T09:00:00+07:00",
      "periodNum": 0,
      "referenceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "providerCode": "string",
      "referenceType": "string",
      "providerOrderRef": "string",
      "recurringContractNo": "string",
      "originalPaymentIntentId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
payment.refundedPAYMENTPROMột khoản hoàn tiền đã hoàn tất. Payload gồm mã hoàn, giao dịch gốc, chứng từ nguồn, số tiền và cách hoàn.

Danh sách trường đang được ghi lại từ sự kiện thật — sẽ hiện ở đây sau lần phát đầu tiên. Ví dụ dưới là phong bì chuẩn.

{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "payment.refunded",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {}
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
order.cancelledSALESPROĐơn hàng bị huỷ.
TrườngKiểu
data.object.totalinteger
data.object.orderIdstring (uuid)
data.object.orderCodestring
data.object.cancelledAtstring (date-time)
data.object.cancelledBystring (uuid)
data.object.channelCodenull
data.object.cancelReasonstring
data.object.crmContactIdstring (uuid)
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "order.cancelled",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "total": 0,
      "orderId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "orderCode": "string",
      "cancelledAt": "2026-10-27T09:00:00+07:00",
      "cancelledBy": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "channelCode": null,
      "cancelReason": "string",
      "crmContactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
order.completedSALESPROĐơn hàng hoàn tất — đã giao và đã thanh toán xong.
TrườngKiểu
data.object.itemsobject[]
data.object.totalstring
data.object.orderIdstring (uuid)
data.object.vatModestring
data.object.branchIdstring (uuid)
data.object.subtotalstring
data.object.orderCodestring
data.object.orderTypestring
data.object.taxAmountstring
data.object.assignedTostring (uuid)
data.object.paidAmountstring
data.object.vatPercentnull
data.object.channelCodestring
data.object.completedAtstring (date-time)
data.object.shippingFeestring
data.object.vatIncludedboolean
data.object.crmContactIdstring (uuid)
data.object.customerNamestring
data.object.customerTypestring
data.object.customerEmailstring
data.object.customerPhonestring
data.object.paymentMethodstring
data.object.discountAmountstring
data.object.customerAddressstring
data.object.customerTaxCodestring
data.object.customerWardCodenull
data.object.sourceContractIdstring (uuid)
data.object.sourceQuotationIdnull
data.object.customerDistrictCodenull
data.object.customerProvinceCodenull
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "order.completed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "items": [
        {
          "total": "string",
          "barcode": "string",
          "serials": null,
          "taxRate": "string",
          "quantity": "string",
          "subtotal": "string",
          "unitCode": "string",
          "unitName": "string",
          "lotNumber": "string",
          "productId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
          "taxAmount": "string",
          "unitPrice": "string",
          "expiryDate": "2026-10-27",
          "productSku": "string",
          "productName": "string",
          "variantName": null,
          "discountAmount": "string",
          "warrantyMonths": 0
        }
      ],
      "total": "string",
      "orderId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "vatMode": "string",
      "branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "subtotal": "string",
      "orderCode": "string",
      "orderType": "string",
      "taxAmount": "string",
      "assignedTo": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "paidAmount": "string",
      "vatPercent": null,
      "channelCode": "string",
      "completedAt": "2026-10-27T09:00:00+07:00",
      "shippingFee": "string",
      "vatIncluded": false,
      "crmContactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "customerName": "string",
      "customerType": "string",
      "customerEmail": "string",
      "customerPhone": "string",
      "paymentMethod": "string",
      "discountAmount": "string",
      "customerAddress": "string",
      "customerTaxCode": "string",
      "customerWardCode": null,
      "sourceContractId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "sourceQuotationId": null,
      "customerDistrictCode": null,
      "customerProvinceCode": null
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}
order.status_changedSALESPROTrạng thái đơn đổi. Bắn cho MỌI lần đổi, kể cả lần đổi sang hoàn tất hoặc huỷ.
TrườngKiểu
data.object.totalinteger
data.object.orderIdstring (uuid)
data.object.branchIdstring (uuid)
data.object.changedAtstring (date-time)
data.object.changedBystring (uuid)
data.object.newStatusstring
data.object.orderCodestring
data.object.orderTypestring
data.object.assignedTostring (uuid)
data.object.channelCodestring
data.object.crmContactIdstring (uuid)
data.object.previousStatusstring
{
  "id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "type": "order.status_changed",
  "api_version": "v1",
  "created_at": "2026-10-27T02:00:00+00:00",
  "workspace_id": "0199a1b2-0000-7000-8000-000000000001",
  "livemode": true,
  "data": {
    "object": {
      "total": 0,
      "orderId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "changedAt": "2026-10-27T09:00:00+07:00",
      "changedBy": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "newStatus": "string",
      "orderCode": "string",
      "orderType": "string",
      "assignedTo": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "channelCode": "string",
      "crmContactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "previousStatus": "string"
    }
  },
  "metadata": {
    "source_service": "string",
    "correlation_id": "string"
  }
}

Chỉ liệt kê sự kiện thực sự được giao. Các tên đã ngừng (deal.created, deal.updated, deal.stage_changed, member.updated, user.updated, order.created, order.confirmed, product.created, product.updated) không bật mới được nữa; endpoint còn giữ chúng vẫn tắt đi được.

4. Cấu trúc payload

Mỗi request có body JSON theo envelope chuẩn:

{
  "id": "evt_018f...d2",          // ID sự kiện (idempotency)
  "type": "contact.created",      // = X-NextX-Event-Type
  "api_version": "v1",
  "created_at": "2026-06-16T03:00:00Z",
  "workspace_id": "<workspace_guid>",
  "livemode": true,               // false nếu là test
  "data": {
    "object": { "id": "...", "...": "..." }   // payload của entity
  }
}

data.object chứa đối tượng của sự kiện. Sự kiện thử có livemode: false. Hiện chưa có tuỳ chọn payload rút gọn: mọi request đều mang đầy đủ data.object.

5. Headers

HeaderÝ nghĩa
X-NextX-Event-IdID sự kiện (GUID trần) — dùng để chống trùng (idempotency). Trường id trong body có dạng evt_ + cùng GUID đó
X-NextX-Event-TypeLoại event, vd contact.created
X-NextX-TimestampUnix seconds lúc ký — kiểm để chống replay
X-NextX-Signature-256sha256=<hex> — HMAC-SHA256 của `timestamp.body` bằng secret hiện tại
X-NextX-Signature-256-PreviousChỉ có trong 7 ngày sau khi xoay secret: sha256=<hex>, cùng timestamp + body nhưng ký bằng secret CŨ
X-NextX-Delivery-AttemptLần giao thứ mấy (1 = lần đầu)
X-NextX-Webhook-IdID của webhook (endpoint) đã đăng ký
webhook-idStandard Webhooks — mã tin nhắn, giữ nguyên qua các lần giao lại (= X-NextX-Event-Id). Chỉ có khi secret dạng whsec_
webhook-timestampStandard Webhooks — Unix seconds lúc ký (= X-NextX-Timestamp)
webhook-signatureStandard Webhooks — v1,<base64> = HMAC-SHA256 của `id.timestamp.body`, khoá = phần sau whsec_ giải mã base64. Lúc xoay secret có 2 signature cách nhau dấu cách

User-Agent: NextX-Webhook/1.0. Header tuỳ chỉnh đặt trên webhook (customHeaders) cũng được gửi kèm.

6. Xác thực signature

Cách nhanh nhất — Standard Webhooks: secret whsec_… theo chuẩn mở Standard Webhooks (standardwebhooks.com), mỗi request có thêm webhook-id, webhook-timestamp, webhook-signature. Dùng thư viện standardwebhooks (Node, Python, Go, Java, C#, PHP, Ruby…) — nó tự verify signature, độ lệch giờ và cả signature kép lúc xoay secret. Webhook tạo trước 14/10/2026 (secret whk_sec_…) chỉ có header X-NextX-*: xoay secret một lần là có thêm header chuẩn.

// Node.js — npm i standardwebhooks
import { Webhook } from "standardwebhooks";

const wh = new Webhook(process.env.NEXTX_WEBHOOK_SECRET);   // "whsec_..."
// Ném lỗi nếu chữ ký sai hoặc timestamp lệch quá 5 phút. headers = header thô của request.
const event = wh.verify(req.rawBody, req.headers);
# Python — pip install standardwebhooks
import os
from standardwebhooks.webhooks import Webhook

wh = Webhook(os.environ["NEXTX_WEBHOOK_SECRET"])   # "whsec_..."
event = wh.verify(raw_body, headers)                # raise nếu signature/timestamp sai

Hoặc tự kiểm bằng HMAC — dùng được với mọi secret, kể cả whk_sec_…:

Ký = HMAC-SHA256(secret, `{ts}.{raw_body}`), xuất hex thường, gắn vào X-NextX-Signature-256: sha256=<hex>. Tính lại bằng raw body (chưa parse JSON) và X-NextX-Timestamp, so khớp; nên từ chối nếu timestamp lệch quá ~5 phút (chống replay).

// Node.js (Express, cần raw body)
import crypto from "crypto";

function verify(req, secret) {
  const ts = req.header("X-NextX-Timestamp");
  const sig = req.header("X-NextX-Signature-256");          // "sha256=..."
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(ts + "." + req.rawBody)                          // timestamp.body
    .digest("hex");
  const ok = sig?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return ok && fresh;
}
# Python
import hmac, hashlib, time

def verify(headers, raw_body, secret):
    ts = headers["X-NextX-Timestamp"]
    sig = headers["X-NextX-Signature-256"]          # "sha256=..."
    expected = "sha256=" + hmac.new(
        secret.encode(), f"{ts}.{raw_body}".encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(sig, expected) and abs(time.time() - int(ts)) < 300

7. Xoay secret (rotate)

Xoay secret (POST /api/v1/webhook/endpoints/{id}/rotate-secret) trả về newSecret và oldSecretValidUntil. Secret cũ vẫn dùng được trong 7 ngày ân hạn: tới mốc đó mọi request mang hai signature trên cùng timestamp.body — X-NextX-Signature-256 ký bằng secret mới và X-NextX-Signature-256-Previous ký bằng secret cũ, cùng định dạng sha256=<hex>.

curl -X POST https://api.nextx.vn/api/v1/webhook/endpoints/<endpoint_id>/rotate-secret \
  -H "Authorization: Bearer <USER_ACCESS_TOKEN>"

{
  "newSecret": "whsec_Mf9KQr8GKYqr...",
  "oldSecretValidUntil": "2026-10-15T03:12:56Z"
}

Nhờ vậy đổi secret không gián đoạn: giữ cả hai secret phía bạn, chấp nhận request khi một trong hai header khớp một trong hai secret, rồi bỏ secret cũ sau khi đã triển khai secret mới (chậm nhất trước oldSecretValidUntil):

// Node.js — accept a request if ANY known secret matches ANY NextX signature header
function verifyDuringRotation(req, secrets /* [newSecret, oldSecret] */) {
  const ts = req.header("X-NextX-Timestamp");
  const sigs = [
    req.header("X-NextX-Signature-256"),             // signed with the NEW secret
    req.header("X-NextX-Signature-256-Previous"),    // signed with the OLD secret (7-day grace only)
  ].filter(Boolean);
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  const ok = secrets.filter(Boolean).some((secret) => {
    const expected = "sha256=" + crypto
      .createHmac("sha256", secret).update(ts + "." + req.rawBody).digest("hex");
    return sigs.some((sig) => sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
  });
  return ok && fresh;
}

Hết ân hạn, header -Previous không còn được gửi và chỉ secret mới verify được. Xoay tiếp khi đang trong ân hạn sẽ mở cửa sổ 7 ngày mới với secret đang dùng lúc đó làm secret "cũ" — secret cũ hơn nữa mất hiệu lực ngay.

8. Giao & retry

  • Thành công = endpoint trả 2xx trong 15s.
  • Tối đa 8 lần giao (lần 1 ngay lập tức, rồi retry sau 1 phút, 5 phút, 30 phút, 2 giờ, 4 giờ, 8 giờ, 12 giờ — tổng khoảng 24 giờ) nếu lỗi/timeout; xem X-NextX-Delivery-Attempt (1 = lần đầu). Hết 8 lần mà vẫn lỗi thì delivery dừng ở trạng thái thất bại.
  • Idempotency: cùng id (evt_…) có thể đến nhiều lần — khử trùng theo id.
  • Circuit breaker: endpoint lỗi liên tiếp từ 100 lần trở lên (ngưỡng mặc định) sẽ bị tự động tắt. Hệ thống không tự bật lại và hiện chưa gửi thông báo khi tắt — bạn cần bật lại bằng POST /endpoints/{id}/enable sau khi sửa lỗi phía bạn.
  • SSRF guard: URL trỏ IP nội bộ/private bị chặn — chỉ nhận HTTPS công khai.
  • Có thể retry thủ công 1 delivery: POST /endpoints/{id}/deliveries/{deliveryId}/retry.

9. Test

Gửi 1 sự kiện thử tới endpoint để kiểm tích hợp:

curl -X POST https://api.nextx.vn/api/v1/webhook/endpoints/<endpoint_id>/test \
  -H "Authorization: Bearer <USER_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{ "eventType": "contact.created" }'

Payload test có livemode: false và data.object giữ chỗ (id: "test_object_id"), không phải dữ liệu thật. Tên sự kiện nào cũng được chấp nhận.