Webhooks
Receive realtime events from Gym Master via HTTP push instead of polling. Each event sends a POST with JSON to your URL, signed with HMAC-SHA256 so you can verify the source.
1. Overview
Webhooks work like Meta's: each workspace has exactly one webhook (one URL) per service — Gym Master, Omnichannel chat. You pick a service, give it an HTTPS URL, then switch on the events of that service one by one. When an event happens in the workspace, Gym Master sends a signed request to the URL. Your endpoint must return 2xx within 15 seconds; otherwise the system redelivers — up to 8 delivery attempts (the first + 7 retries over ~24 hours), with increasing backoff (see Delivery & retry).
Authorization: Bearer <USER_ACCESS_TOKEN> of a user holding webhook.endpoints.manage (listing needs webhook.endpoints.view, delivery logs webhook.deliveries.view / webhook.deliveries.manage). An application's OAuth token (client_credentials) and API keys cannot call these APIs: the webhook module is not granted to applications. An app/agent can guide the customer, or the customer sets it up in the app.2. Set up a webhook per service
In the app: Settings › Apps & API › Webhooks → pick a service → paste the URL → Verify and save → switch on each event you need. The steps below are the same flow over the API.
Step 1 — List services and their events
curl https://api.nextx.vn/api/v1/webhook/services \
-H "Authorization: Bearer <USER_ACCESS_TOKEN>"Returns services[]: each service has a service code (GYM, COMM), a displayName, its events[] (eventType, displayName, description) and endpoint — the current webhook, or null if not configured yet. legacy[] lists old-style endpoints (created without a service) that have not been deleted.
Step 2 — Verify the URL (recommended)
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/gym", "verifyToken": "my-random-token" }'Gym Master sends a POST with type: "endpoint.verification", a random challenge and your verify_token. Your server must reply 2xx with the challenge string somewhere in the response body. Nothing is saved by this call; the result is success, responseStatus, durationMs, challenge, responseBody, errorMessage and failureReason (0 OK · 1 unreachable — DNS/TLS/timeout/private IP · 2 HTTP error · 3 returned 2xx but did not echo the challenge).
// Node.js (Express) — answer the verification ping BEFORE checking the signature
app.post("/webhooks/gym", 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);
});The verification request is signed with a one-time secret (there is no real secret yet), so it will not pass your signature check — handle endpoint.verification first, as above. Echoing the challenge reveals nothing secret.
Step 3 — Set the service's URL (create or update)
curl -X PUT https://api.nextx.vn/api/v1/webhook/services/GYM \
-H "Authorization: Bearer <USER_ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/gym",
"subscribedEvents": ["member.registered", "checkin.completed"]
}'PUT /api/v1/webhook/services/{service} with body { url, subscribedEvents?, customHeaders? }. The first call creates the webhook (201, created: true) and returns signingSecret (whk_sec_…) only this once — store it to verify signatures:
HTTP/1.1 201 Created
{
"id": "<endpoint_id>",
"service": "GYM",
"url": "https://your-app.com/webhooks/gym",
"status": "Active",
"subscribedEvents": ["member.registered", "checkin.completed"],
"created": true,
"signingSecret": "whk_sec_3f9a...c21",
"createdAt": "2026-10-08T03:12:56Z",
"updatedAt": "2026-10-08T03:12:56Z"
}Calling it again updates the URL (200, created: false, no secret). On update, omitting subscribedEvents / customHeaders keeps the current value; on create, an empty or missing list means no events yet — the webhook receives nothing until you switch events on.
Step 4 — Switch events on/off, or remove the webhook
# Turn one event on (or off with "subscribed": false)
curl -X PUT https://api.nextx.vn/api/v1/webhook/services/GYM/events/contract.expiring \
-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/GYM \
-H "Authorization: Bearer <USER_ACCESS_TOKEN>"- Service codes are upper-case and matched exactly (
GYM, notgym). A code with no active events →404 webhook.service.not_found. - An event that belongs to another service →
422 webhook.event.not_in_service(e.g.contact.identifiedonGYM); an unknown event →422as well. - Switching an event or deleting when the service has no webhook yet →
404 webhook.service.not_configured. - The old
POST /api/v1/webhook/endpointsstill works but now requiresservice; a service that already has a webhook →409 webhook.endpoint.service_exists. A body mixing events of several services with noserviceis rejected — use onePUT services/{service}per service instead.
By-id operations still apply to a service's webhook (its id is in endpoint.id): GET /endpoints/{id}, POST /endpoints/{id}/disable / /enable, POST /endpoints/{id}/rotate-secret, POST /endpoints/{id}/test, and the delivery log at /endpoints/{id}/deliveries.
3. Event catalog
51 events across 10 services. The Source column is the service code — a service's webhook can only subscribe to that service's events.
CRM · CRMSALES · SalesIAM · Accounts & BranchesOFFICE · OfficeCOMM · Omnichannel chatCALL · Call centerHRM · HRPAYMENT · PaymentsEINVOICE · E-invoicesGYM · Gym Masteragent.status_changedCALLPROAn agent deliberately changed status (available / paused / offline) from the supervisor screen or the partner API.
| Field | Type |
|---|---|
data.object.to | string |
data.object.from | string |
data.object.agent | object |
data.object.reason | string |
data.object.changedAt | string (date-time) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "agent.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": {
"to": "string",
"from": "string",
"agent": {
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"name": "string",
"email": "[email protected]",
"userId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"extension": "string"
},
"reason": "string",
"changedAt": "2026-10-27T09:00:00+07:00"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}call.answeredCALLPROThe call was answered (status=in-progress). The payload includes the agent on the call.
| Field | Type |
|---|---|
data.object.to | string |
data.object.from | string |
data.object.agent | object |
data.object.callId | string (uuid) |
data.object.status | string |
data.object.endedAt | null |
data.object.queueId | null |
data.object.talkSec | null |
data.object.waitSec | integer |
data.object.metadata | null |
data.object.sequence | integer |
data.object.direction | string |
data.object.queueName | null |
data.object.startedAt | string (date-time) |
data.object.answeredAt | string (date-time) |
data.object.contactRef | null |
data.object.hasSummary | boolean |
data.object.durationSec | null |
data.object.hangupCause | null |
data.object.crmContactId | null |
data.object.hasRecording | boolean |
data.object.hangupCauseName | null |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "call.answered",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"to": "string",
"from": "string",
"agent": {
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"name": "string",
"email": "[email protected]",
"userId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"extension": "string"
},
"callId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "string",
"endedAt": null,
"queueId": null,
"talkSec": null,
"waitSec": 0,
"metadata": null,
"sequence": 0,
"direction": "string",
"queueName": null,
"startedAt": "2026-10-27T09:00:00+07:00",
"answeredAt": "2026-10-27T09:00:00+07:00",
"contactRef": null,
"hasSummary": false,
"durationSec": null,
"hangupCause": null,
"crmContactId": null,
"hasRecording": false,
"hangupCauseName": null
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}call.completedCALLPROThe call ended. The payload includes durations, the final status (completed / busy / no-answer / failed / canceled / abandoned) and the Q.850 hangupCause.
| Field | Type |
|---|---|
data.object.to | string |
data.object.from | string |
data.object.agent | object |
data.object.callId | string (uuid) |
data.object.status | string |
data.object.endedAt | string (date-time) |
data.object.queueId | null |
data.object.talkSec | integer |
data.object.waitSec | integer |
data.object.metadata | null |
data.object.sequence | integer |
data.object.direction | string |
data.object.queueName | null |
data.object.startedAt | string (date-time) |
data.object.answeredAt | string (date-time) |
data.object.contactRef | null |
data.object.hasSummary | boolean |
data.object.durationSec | integer |
data.object.hangupCause | integer |
data.object.crmContactId | null |
data.object.hasRecording | boolean |
data.object.hangupCauseName | string |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "call.completed",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"to": "string",
"from": "string",
"agent": {
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"name": "string",
"email": "[email protected]",
"userId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"extension": "string"
},
"callId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "string",
"endedAt": "2026-10-27T09:00:00+07:00",
"queueId": null,
"talkSec": 0,
"waitSec": 0,
"metadata": null,
"sequence": 0,
"direction": "string",
"queueName": null,
"startedAt": "2026-10-27T09:00:00+07:00",
"answeredAt": "2026-10-27T09:00:00+07:00",
"contactRef": null,
"hasSummary": false,
"durationSec": 0,
"hangupCause": 0,
"crmContactId": null,
"hasRecording": false,
"hangupCauseName": "string"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}call.missedCALLPROThe call ended without anyone answering (missed call, caller gave up in the queue, or an outbound call nobody picked up).
| Field | Type |
|---|---|
data.object.to | string |
data.object.from | string |
data.object.agent | object |
data.object.callId | string (uuid) |
data.object.status | string |
data.object.endedAt | string (date-time) |
data.object.queueId | null |
data.object.talkSec | null |
data.object.waitSec | null |
data.object.metadata | object |
data.object.sequence | integer |
data.object.direction | string |
data.object.queueName | null |
data.object.startedAt | string (date-time) |
data.object.answeredAt | null |
data.object.contactRef | string |
data.object.hasSummary | boolean |
data.object.durationSec | integer |
data.object.hangupCause | integer |
data.object.crmContactId | string (uuid) |
data.object.hasRecording | boolean |
data.object.hangupCauseName | string |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "call.missed",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"to": "string",
"from": "string",
"agent": {
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"name": "string",
"email": "[email protected]",
"userId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"extension": "string"
},
"callId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "string",
"endedAt": "2026-10-27T09:00:00+07:00",
"queueId": null,
"talkSec": null,
"waitSec": null,
"metadata": {},
"sequence": 0,
"direction": "string",
"queueName": null,
"startedAt": "2026-10-27T09:00:00+07:00",
"answeredAt": null,
"contactRef": "string",
"hasSummary": false,
"durationSec": 0,
"hangupCause": 0,
"crmContactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"hasRecording": false,
"hangupCauseName": "string"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}call.recording_readyCALLPROThe call recording is ready. No link is included — call GET /api/v1/call/partner/calls/{id}/recording for a 15-minute link.
| Field | Type |
|---|---|
data.object.to | string |
data.object.from | string |
data.object.agent | object |
data.object.callId | string (uuid) |
data.object.status | string |
data.object.endedAt | string (date-time) |
data.object.queueId | null |
data.object.talkSec | integer |
data.object.waitSec | integer |
data.object.metadata | null |
data.object.sequence | integer |
data.object.direction | string |
data.object.queueName | null |
data.object.startedAt | string (date-time) |
data.object.answeredAt | string (date-time) |
data.object.contactRef | null |
data.object.hasSummary | boolean |
data.object.durationSec | integer |
data.object.hangupCause | integer |
data.object.crmContactId | null |
data.object.hasRecording | boolean |
data.object.hangupCauseName | string |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "call.recording_ready",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"to": "string",
"from": "string",
"agent": {
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"name": "string",
"email": "[email protected]",
"userId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"extension": "string"
},
"callId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "string",
"endedAt": "2026-10-27T09:00:00+07:00",
"queueId": null,
"talkSec": 0,
"waitSec": 0,
"metadata": null,
"sequence": 0,
"direction": "string",
"queueName": null,
"startedAt": "2026-10-27T09:00:00+07:00",
"answeredAt": "2026-10-27T09:00:00+07:00",
"contactRef": null,
"hasSummary": false,
"durationSec": 0,
"hangupCause": 0,
"crmContactId": null,
"hasRecording": false,
"hangupCauseName": "string"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}call.ringingCALLPROA call is ringing: an inbound call (status=queued while waiting in a queue, ringing once it reaches an agent) or an outbound call that just started. Use it to pop up the customer record.
| Field | Type |
|---|---|
data.object.to | string |
data.object.from | string |
data.object.agent | object |
data.object.callId | string (uuid) |
data.object.status | string |
data.object.endedAt | null |
data.object.queueId | null |
data.object.talkSec | null |
data.object.waitSec | null |
data.object.metadata | object |
data.object.sequence | integer |
data.object.direction | string |
data.object.queueName | null |
data.object.startedAt | string (date-time) |
data.object.answeredAt | null |
data.object.contactRef | string |
data.object.hasSummary | boolean |
data.object.durationSec | null |
data.object.hangupCause | null |
data.object.crmContactId | null |
data.object.hasRecording | boolean |
data.object.hangupCauseName | null |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "call.ringing",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"to": "string",
"from": "string",
"agent": {
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"name": "string",
"email": "[email protected]",
"userId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"extension": "string"
},
"callId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "string",
"endedAt": null,
"queueId": null,
"talkSec": null,
"waitSec": null,
"metadata": {},
"sequence": 0,
"direction": "string",
"queueName": null,
"startedAt": "2026-10-27T09:00:00+07:00",
"answeredAt": null,
"contactRef": "string",
"hasSummary": false,
"durationSec": null,
"hangupCause": null,
"crmContactId": null,
"hasRecording": false,
"hangupCauseName": null
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}call.summary_readyCALLPROThe AI summary of the call is ready — it is in data.object.summary (also returned by GET /api/v1/call/partner/calls/{id}).
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "call.summary_ready",
"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"
}
}contact.identifiedCOMMPROA customer just sent a phone number in chat, with the conversation source (ad / UTM) and the assigned staff member
| Field | Type |
|---|---|
data.object.email | string (email) |
data.object.phone | string |
data.object.agentId | string (uuid) |
data.object.attribution | object |
data.object.channelType | string |
data.object.displayName | string |
data.object.workspaceId | string (uuid) |
data.object.crmContactId | string (uuid) |
data.object.identifiedAt | string (date-time) |
data.object.conversationId | string (uuid) |
data.object.channelAccountId | string (uuid) |
data.object.channelContactId | string (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": "[email protected]",
"phone": "string",
"agentId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"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"
},
"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_resolvedCOMMPROThe conversation's ad tree (ad set, campaign) was just fetched from Meta, usually a few minutes after the event above; same conversationId — an overwrite, not a new record
| Field | Type |
|---|---|
data.object.resolvedAt | string (date-time) |
data.object.attribution | object |
data.object.workspaceId | string (uuid) |
data.object.conversationId | string (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.createdCRMPROA new contact was created in the CRM — manually, by file import, or automatically from chat / web forms
| Field | Type |
|---|---|
data.object.code | string |
data.object.name | string |
data.object.type | string |
data.object.email | string |
data.object.phone | string |
data.object.source | string |
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.createdAt | string (date-time) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "member.registered",
"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.deletedCRMPROA contact was deleted from the CRM
| Field | Type |
|---|---|
data.object.contactId | string (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.updatedCRMPROA contact was edited; the payload is the state AFTER the edit, not a diff
| Field | Type |
|---|---|
data.object.city | string |
data.object.code | string |
data.object.ward | string |
data.object.email | string |
data.object.phone | string |
data.object.address | string |
data.object.taxCode | string |
data.object.branchId | string (uuid) |
data.object.fullName | string |
data.object.sourceId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.assigneeId | string (uuid) |
data.object.sourceName | string |
data.object.changedFields | string[] |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "checkin.completed",
"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.wonCRMPROAn opportunity moved to won (does not fire for lost or cancelled opportunities)
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "contract.expiring",
"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.cancelledEINVOICEPROAn e-invoice was cancelled — on Gym Master or detected as cancelled at the provider during sync. Payload includes the cancellation reason.
| Field | Type |
|---|---|
data.object.status | string |
data.object.cqtCode | null |
data.object.issuedAt | null |
data.object.invoiceId | string (uuid) |
data.object.referenceId | string (uuid) |
data.object.cancelReason | string |
data.object.errorMessage | null |
data.object.invoiceNumber | null |
data.object.referenceCode | string |
data.object.referenceType | string |
{
"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.failedEINVOICEPROIssuing an e-invoice failed. Payload includes the source document and the error message returned by the e-invoice provider.
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.issuedEINVOICEPROAn e-invoice was issued successfully. Payload includes the source document (referenceType / referenceId / referenceCode), invoice number, tax authority code (if any) and issue time.
| Field | Type |
|---|---|
data.object.status | string |
data.object.cqtCode | string |
data.object.issuedAt | string (date-time) |
data.object.invoiceId | string (uuid) |
data.object.referenceId | string (uuid) |
data.object.cancelReason | null |
data.object.errorMessage | null |
data.object.invoiceNumber | string |
data.object.referenceCode | string |
data.object.referenceType | string |
{
"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"
}
}booking.confirmedGYMPROA booking was confirmed — class, PT session or consultation; payload: booking type, date, time slot, trainer (members promoted from the waitlist do not fire yet)
| Field | Type |
|---|---|
data.object.refId | string (uuid) |
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.bookingType | string |
data.object.sessionDate | string (date) |
data.object.gymProfileId | string (uuid) |
data.object.timeslotLabel | string |
data.object.instructorName | string |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "booking.confirmed",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"refId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"bookingType": "string",
"sessionDate": "2026-10-27",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"timeslotLabel": "string",
"instructorName": "string"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}checkin.completedGYMPROA member checked in successfully at the gym
| Field | Type |
|---|---|
data.object.branchId | string (uuid) |
data.object.checkInId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.checkinTime | string (date-time) |
data.object.checkinType | string |
data.object.packageName | string |
data.object.trainerName | string |
data.object.contractCode | string |
data.object.gymProfileId | string (uuid) |
data.object.subscriptionId | string (uuid) |
data.object.sessionsRemaining | integer |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "checkin.completed",
"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",
"checkInId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"checkinTime": "2026-10-27T09:00:00+07:00",
"checkinType": "string",
"packageName": "string",
"trainerName": "string",
"contractCode": "string",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"subscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"sessionsRemaining": 0
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}contract.cancelledGYMPROA contract was cancelled; payload: contract id and member, no cancellation reason
| Field | Type |
|---|---|
data.object.contactId | string (uuid) |
data.object.contractCode | string |
data.object.gymProfileId | string (uuid) |
data.object.subscriptionId | string (uuid) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "contract.cancelled",
"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",
"contractCode": "string",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"subscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}contract.expiringGYMPROA contract is about to expire (automatic reminder before the expiry date); payload: expiry date, days left, plan name, expected renewal amount
| Field | Type |
|---|---|
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.contractId | string (uuid) |
data.object.expiryDate | string (date) |
data.object.packageName | string |
data.object.gymProfileId | string (uuid) |
data.object.renewalAmount | string |
data.object.contractPeriod | string |
data.object.daysUntilExpiry | integer |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "contract.expiring",
"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",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contractId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"expiryDate": "2026-10-27",
"packageName": "string",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"renewalAmount": "string",
"contractPeriod": "string",
"daysUntilExpiry": 0
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}contract.frozenGYMPROA contract was frozen and the freeze took effect (including scheduled freezes on their start date); the reason is not included
| Field | Type |
|---|---|
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.freezeDate | string (date) |
data.object.freezeDays | integer |
data.object.gymProfileId | string (uuid) |
data.object.subscriptionId | string (uuid) |
data.object.plannedResumeDate | string (date) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "contract.frozen",
"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",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"freezeDate": "2026-10-27",
"freezeDays": 0,
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"subscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"plannedResumeDate": "2026-10-27"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}contract.unfrozenGYMPROA contract came out of a freeze; payload: unused freeze days and the new expiry date
| Field | Type |
|---|---|
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.unusedDays | integer |
data.object.gymProfileId | string (uuid) |
data.object.newExpireDate | string (date) |
data.object.freezeRecordId | string (uuid) |
data.object.subscriptionId | string (uuid) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "contract.unfrozen",
"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",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"unusedDays": 0,
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"newExpireDate": "2026-10-27",
"freezeRecordId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"subscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}member.registeredGYMPROA new member registered
| Field | Type |
|---|---|
data.object.city | string |
data.object.ward | string |
data.object.email | string |
data.object.phone | string |
data.object.gender | string |
data.object.phone2 | string |
data.object.phone3 | null |
data.object.address | string |
data.object.country | string |
data.object.branchId | string (uuid) |
data.object.fullName | string |
data.object.sourceId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.industryId | null |
data.object.joinedDate | string (date) |
data.object.memberCode | string |
data.object.dateOfBirth | string (date) |
data.object.gymProfileId | string (uuid) |
data.object.identityNumber | string |
data.object.customerGroupId | string (uuid) |
data.object.contactPersonName | string |
data.object.contactPersonPhone | string |
data.object.identityIssuedDate | string (date) |
data.object.identityIssuedPlace | string |
data.object.contactPersonRelationship | string |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "member.registered",
"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",
"ward": "string",
"email": "string",
"phone": "string",
"gender": "string",
"phone2": "string",
"phone3": null,
"address": "string",
"country": "string",
"branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"fullName": "string",
"sourceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"industryId": null,
"joinedDate": "2026-10-27",
"memberCode": "string",
"dateOfBirth": "2026-10-27",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"identityNumber": "string",
"customerGroupId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactPersonName": "string",
"contactPersonPhone": "string",
"identityIssuedDate": "2026-10-27",
"identityIssuedPlace": "string",
"contactPersonRelationship": "string"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}payment.receivedGYMPROA member payment was recorded
| Field | Type |
|---|---|
data.object.amount | integer |
data.object.source | string |
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.vatPercent | string |
data.object.contactIdNo | string |
data.object.contactName | string |
data.object.description | string |
data.object.sourceRefId | string (uuid) |
data.object.vatIncluded | boolean |
data.object.buyerTaxCode | string |
data.object.contactEmail | string |
data.object.contactPhone | string |
data.object.contractCode | string |
data.object.gymProfileId | string (uuid) |
data.object.contactIdType | string |
data.object.paymentMethod | string |
data.object.transactionId | string (uuid) |
data.object.contactAddress | string |
data.object.sourceCategory | string |
data.object.subscriptionId | string (uuid) |
data.object.transactionDate | string (date) |
data.object.transactionType | string |
data.object.buyerCompanyName | string |
data.object.buyerContactName | string |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "payment.received",
"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,
"source": "string",
"branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"vatPercent": "string",
"contactIdNo": "string",
"contactName": "string",
"description": "string",
"sourceRefId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"vatIncluded": false,
"buyerTaxCode": "string",
"contactEmail": "string",
"contactPhone": "string",
"contractCode": "string",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactIdType": "string",
"paymentMethod": "string",
"transactionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactAddress": "string",
"sourceCategory": "string",
"subscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"transactionDate": "2026-10-27",
"transactionType": "string",
"buyerCompanyName": "string",
"buyerContactName": "string"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}subscription.expiredGYMPROA contract expired (moved to expired automatically); payload: contract, member, branch, expiry date, plan name
| Field | Type |
|---|---|
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.expiredDate | string (date) |
data.object.packageName | string |
data.object.packageType | string |
data.object.gymProfileId | string (uuid) |
data.object.subscriptionId | string (uuid) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "subscription.expired",
"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",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"expiredDate": "2026-10-27",
"packageName": "string",
"packageType": "string",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"subscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}subscription.purchasedGYMPROA contract / membership plan takes effect — created as active, a pending contract activated, or an online payment completed (contracts created as pending do not fire). Payload: contract id, member, branch, plan name, amount, start / end dates
| Field | Type |
|---|---|
data.object.endDate | string (date) |
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.pricePaid | integer |
data.object.startDate | string (date) |
data.object.packageName | string |
data.object.packageType | string |
data.object.gymProfileId | string (uuid) |
data.object.subscriptionId | string (uuid) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "subscription.purchased",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"endDate": "2026-10-27",
"branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"pricePaid": 0,
"startDate": "2026-10-27",
"packageName": "string",
"packageType": "string",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"subscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}subscription.renewedGYMPROA contract was renewed; payload carries the new and previous contract ids, plan name, amount and the new validity period
| Field | Type |
|---|---|
data.object.endDate | string (date) |
data.object.branchId | string (uuid) |
data.object.contactId | string (uuid) |
data.object.pricePaid | integer |
data.object.startDate | string (date) |
data.object.packageName | string |
data.object.gymProfileId | string (uuid) |
data.object.newSubscriptionId | string (uuid) |
data.object.oldSubscriptionId | string (uuid) |
{
"id": "evt_0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "subscription.renewed",
"api_version": "v1",
"created_at": "2026-10-27T02:00:00+00:00",
"workspace_id": "0199a1b2-0000-7000-8000-000000000001",
"livemode": true,
"data": {
"object": {
"endDate": "2026-10-27",
"branchId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"contactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"pricePaid": 0,
"startDate": "2026-10-27",
"packageName": "string",
"gymProfileId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"newSubscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"oldSubscriptionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}employee.createdHRMPROA new employee was created in HRM. Payload includes code, full name, employee type, start date, department, job title and the first contract type.
| Field | Type |
|---|---|
data.object.code | string |
data.object.fullName | string |
data.object.joinDate | string (date) |
data.object.orgUnitId | string (uuid) |
data.object.employeeId | string (uuid) |
data.object.positionId | string (uuid) |
data.object.employeeType | string |
data.object.initialContractType | string |
{
"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.terminatedHRMPROAn employee was terminated. Payload includes code, full name, last working date and decision number; the reason is not included.
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.updatedHRMPROAn employee profile was edited. The payload is the state AFTER the edit (code, full name, email, department, job title).
| Field | Type |
|---|---|
data.object.code | string |
data.object.email | string (email) |
data.object.fullName | string |
data.object.orgUnitId | string (uuid) |
data.object.updatedAt | string (date-time) |
data.object.employeeId | string (uuid) |
data.object.positionId | string (uuid) |
data.object.workspaceId | string (uuid) |
{
"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": {
"code": "string",
"email": "[email protected]",
"fullName": "string",
"orgUnitId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"updatedAt": "2026-10-27T09:00:00+07:00",
"employeeId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"positionId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}labor_contract.evaluatedHRMPROA labor contract was evaluated. Payload includes contract type, decision, total / average score and the next contract if any.
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.approvedHRMPROA leave request was approved at the final step. Payload includes employee, leave type, code and date range; approval notes are not included.
| Field | Type |
|---|---|
data.object.toDate | string (date) |
data.object.leaveId | string (uuid) |
data.object.fromDate | string (date) |
data.object.leaveType | string |
data.object.approvedAt | string (date-time) |
data.object.employeeId | string (uuid) |
data.object.leaveSymbol | string |
data.object.workspaceId | string (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.rejectedHRMPROA leave request was rejected. Payload includes employee, leave type and date range; the rejection reason is not included.
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.lockedHRMPROA monthly payroll was locked. Payload includes year, month, lock time and who locked it; no amounts.
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.acknowledgedHRMPROAn employee signed to acknowledge a payslip. Payload includes payslip, employee, month / year and signing time.
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.hiredHRMPROA candidate was hired. Payload includes application, candidate, job posting, hire date and the created employee (if any).
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.createdIAMPROA new branch was created
| Field | Type |
|---|---|
data.object.code | string |
data.object.name | string |
data.object.address | string |
data.object.branchId | string (uuid) |
data.object.parentId | null |
data.object.managerId | null |
{
"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.updatedIAMPROA branch was edited
| Field | Type |
|---|---|
data.object.branchId | string (uuid) |
data.object.changedFields | string[] |
{
"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.createdIAMPROA new user was added to the workspace
| Field | Type |
|---|---|
data.object.email | string |
data.object.userId | string (uuid) |
data.object.fullName | string |
data.object.createdAt | string (date-time) |
data.object.workspaceId | string (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.createdIAMPROA new workspace was created
| Field | Type |
|---|---|
data.object.code | string |
data.object.name | string |
data.object.locale | string |
data.object.ownerId | string (uuid) |
data.object.timezone | string |
data.object.createdAt | string (date-time) |
data.object.ownerEmail | string (email) |
data.object.workspaceId | string (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": "[email protected]",
"workspaceId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}approval.instance.status_changedOFFICEPROAn approval request changed status (pending / approved / rejected / withdrawn)
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.completedOFFICEPROAn approver finished their approval step
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.createdOFFICEPROAn approval step was assigned to an approver
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.completedPAYMENTPROA buyer paid successfully through an online gateway (QR, card, wallet, pay-later…) or a manual collection was confirmed. Payload includes transaction code, source document (referenceType / referenceId — sales order, gym contract…), gateway, amount and paid time.
| Field | Type |
|---|---|
data.object.amount | integer |
data.object.paidAt | string (date-time) |
data.object.gateway | string |
data.object.payerId | string (uuid) |
data.object.currency | string |
data.object.metadata | object |
data.object.netAmount | integer |
data.object.payerType | string |
data.object.referenceId | string (uuid) |
data.object.workspaceId | string (uuid) |
data.object.eInvoiceHint | null |
data.object.providerCode | string |
data.object.referenceType | string |
data.object.transactionId | string |
data.object.dbTransactionId | string (uuid) |
data.object.paymentIntentId | string (uuid) |
data.object.paymentInstrument | string |
data.object.providerApprovalCode | string |
data.object.providerTransactionId | string |
data.object.paymentInstrumentDetail | string |
{
"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.failedPAYMENTPROAn online payment attempt failed (gateway declined, buyer cancelled, expired). Payload includes source document, gateway and the reason returned by the gateway.
| Field | Type |
|---|---|
data.object.reason | string |
data.object.failedAt | string (date-time) |
data.object.referenceId | string (uuid) |
data.object.workspaceId | string (uuid) |
data.object.providerCode | string |
data.object.referenceType | string |
data.object.paymentIntentId | string (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_chargedPAYMENTPROThe gateway successfully charged one period of a recurring payment agreement. Payload has the agreement number and period number (periodNum) — the gateway may report the same period more than once, so deduplicate on this pair.
| Field | Type |
|---|---|
data.object.amount | integer |
data.object.paidAt | string (date-time) |
data.object.payerId | string (uuid) |
data.object.currency | string |
data.object.metadata | object |
data.object.payerType | string |
data.object.periodNum | integer |
data.object.referenceId | string (uuid) |
data.object.workspaceId | string (uuid) |
data.object.providerCode | string |
data.object.referenceType | string |
data.object.dbTransactionId | string (uuid) |
data.object.paymentIntentId | string (uuid) |
data.object.paymentInstrument | string |
data.object.recurringContractNo | string |
data.object.providerApprovalCode | null |
data.object.providerTransactionId | string |
data.object.originalPaymentIntentId | string (uuid) |
data.object.paymentInstrumentDetail | string |
{
"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_failedPAYMENTPROThe gateway tried to charge one recurring period but failed. Payload includes agreement number, period number, the charge order code and the reason.
| Field | Type |
|---|---|
data.object.reason | string |
data.object.failedAt | string (date-time) |
data.object.periodNum | integer |
data.object.referenceId | string (uuid) |
data.object.workspaceId | string (uuid) |
data.object.providerCode | string |
data.object.referenceType | string |
data.object.providerOrderRef | string |
data.object.recurringContractNo | string |
data.object.originalPaymentIntentId | string (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.refundedPAYMENTPROA refund was completed. Payload includes refund code, original transaction, source document, amount and refund method.
Fields are being recorded from real events and will appear here after the first delivery. The example below is the standard envelope.
{
"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.cancelledSALESPROAn order was cancelled
| Field | Type |
|---|---|
data.object.total | integer |
data.object.orderId | string (uuid) |
data.object.orderCode | string |
data.object.cancelledAt | string (date-time) |
data.object.cancelledBy | string (uuid) |
data.object.channelCode | string |
data.object.cancelReason | string |
data.object.crmContactId | string (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": "string",
"cancelReason": "string",
"crmContactId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
},
"metadata": {
"source_service": "string",
"correlation_id": "string"
}
}order.completedSALESPROAn order was completed — delivered and fully paid
| Field | Type |
|---|---|
data.object.items | object[] |
data.object.total | string |
data.object.orderId | string (uuid) |
data.object.vatMode | string |
data.object.branchId | string (uuid) |
data.object.subtotal | string |
data.object.orderCode | string |
data.object.orderType | string |
data.object.taxAmount | string |
data.object.assignedTo | string (uuid) |
data.object.paidAmount | string |
data.object.vatPercent | null |
data.object.channelCode | string |
data.object.completedAt | string (date-time) |
data.object.shippingFee | string |
data.object.vatIncluded | boolean |
data.object.crmContactId | string (uuid) |
data.object.customerName | string |
data.object.customerType | string |
data.object.customerEmail | string |
data.object.customerPhone | string |
data.object.paymentMethod | string |
data.object.discountAmount | string |
data.object.customerAddress | string |
data.object.customerTaxCode | string |
data.object.customerWardCode | null |
data.object.sourceContractId | string (uuid) |
data.object.sourceQuotationId | null |
data.object.customerDistrictCode | null |
data.object.customerProvinceCode | null |
{
"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_changedSALESPROAn order changed status; fires on EVERY change, including to completed or cancelled
| Field | Type |
|---|---|
data.object.total | integer |
data.object.orderId | string (uuid) |
data.object.branchId | string (uuid) |
data.object.changedAt | string (date-time) |
data.object.changedBy | string (uuid) |
data.object.newStatus | string |
data.object.orderCode | string |
data.object.orderType | string |
data.object.assignedTo | string (uuid) |
data.object.channelCode | string |
data.object.crmContactId | string (uuid) |
data.object.previousStatus | string |
{
"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"
}
}Only events that are actually delivered are listed. Retired names (deal.created, deal.updated, deal.stage_changed, member.updated, user.updated, order.created, order.confirmed, product.created, product.updated) can no longer be subscribed to; an endpoint that still has them can switch them off.
4. Payload structure
Each request has a JSON body in the standard envelope:
{
"id": "evt_018f...d2", // ID sự kiện (idempotency)
"type": "member.registered", // = 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 carries the event's object. Test events have livemode: false. There is currently no slim-payload option: every request carries the full data.object.
5. Headers
| Header | Meaning |
|---|---|
webhook-id | Standard Webhooks — message id, stable across redeliveries |
webhook-timestamp | Standard Webhooks — Unix seconds at signing time |
webhook-signature | Standard Webhooks — v1,<base64> = HMAC-SHA256 of `id.timestamp.body`, key = base64-decoded part after whsec_. Two space-separated signatures during rotation |
User-Agent: NextX-Webhook/1.0. Custom headers set on the webhook (customHeaders) are also sent.
6. Verifying the signature
Fastest — Standard Webhooks: whsec_… secrets follow the open Standard Webhooks spec (standardwebhooks.com); each request also carries webhook-id, webhook-timestamp, webhook-signature. Use the standardwebhooks library (Node, Python, Go, Java, C#, PHP, Ruby…) — it checks the signature, clock skew and the dual signature during rotation for you. Webhooks created before 14/10/2026 (whk_sec_… secret) only have the X-NextX-* headers: rotate the secret once to get the standard headers too.
// 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 saiOr verify the HMAC yourself — works with every secret, including whk_sec_…:
Signature = HMAC-SHA256(secret, `{ts}.{raw_body}`), output as lowercase hex, placed in X-NextX-Signature-256: sha256=<hex>. Recompute using the raw body (before JSON parsing) and X-NextX-Timestamp, then compare; reject if the timestamp drifts more than ~5 minutes (replay protection).
// 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)) < 3007. Rotating the secret
Rotating (POST /api/v1/webhook/endpoints/{id}/rotate-secret) returns newSecret and oldSecretValidUntil. The old secret keeps working for a 7-day grace period: until that moment every request carries two signatures over the same timestamp.body — X-NextX-Signature-256 with the new secret and X-NextX-Signature-256-Previous with the old one, same sha256=<hex> format.
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"
}So you can switch without downtime: keep both secrets on your side, accept a request when either header matches either secret, then drop the old secret once you have deployed the new one (at the latest by oldSecretValidUntil):
// Node.js — accept a request if ANY known secret matches ANY 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;
}After the grace period the -Previous header is no longer sent and only the new secret verifies. Rotating again during a grace period starts a new 7-day window with the secret that was current at that time as the "previous" one — the older secret stops verifying immediately.
8. Delivery & retry
- Success = the endpoint returns
2xxwithin 15s. - Up to 8 delivery attempts (attempt 1 immediately, then retries after 1 min, 5 min, 30 min, 2 h, 4 h, 8 h, 12 h — about 24 hours in total) on error/timeout; see
X-NextX-Delivery-Attempt(1 = first). After 8 failed attempts the delivery stays failed. - Idempotency: the same
id(evt_…) may arrive multiple times — dedupe byid. - Circuit breaker: an endpoint that fails 100 times in a row or more (the default threshold) is switched off automatically. It is not switched back on automatically and no notification is sent yet — re-enable it with
POST /endpoints/{id}/enableonce you have fixed the problem on your side. - SSRF guard: URLs pointing at internal/private IPs are blocked — only public HTTPS is accepted.
- You can retry one delivery manually:
POST /endpoints/{id}/deliveries/{deliveryId}/retry.
9. Test
Send a test event to your endpoint to check the integration:
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": "member.registered" }'The test payload has livemode: false and a placeholder data.object (id: "test_object_id"), not real data. Any event name is accepted.