Introduction
Many entities can be subscribed to so that when triggers occur like updates or new records, call-outs can be made to other API listeners (like that of a client).
Very high level flow (can share with customers)
How to subscribe to, and receive call-outs about an Entity
In this walkthrough we’ll look at a scenario where a third-party wants to receive API calls every time that a new Employee belonging to a particular region is created in the TrackTik Cloud Portal.
1 - Identify the Entity
The entity in this scenario is Employees. In an event subscription, this is written in a domain:entity format like:
entity:employee
2 - Identify the Trigger
In this scenario, we want the trigger to be on created.
The trigger for our scenario will be defined in the event subscription using this convention:
entity:employee:created
The other possible trigger choices are updated or the name of one of the endpoint's actions.
NB: There can only be one entity targeted in the events list, but the number of triggers for that event isn't limited. So you can have employees:created and employees:updated, but not two different entities like employees:created and clients:created. Each entity requires its own subscription.
You can retrieve a full list of possibilities at https://<TrackTik Portal domain name>/rest/v1/event-types .
3 - Identify the Regional Context
TrackTik Cloud Portals have regions and you need to know what region the data for your entity data is referentially linked to, so that you don’t trigger call-outs about too much information.
In this scenario, we’ll assume there is a Region called “Health Care”. We’ll need to get its ID number:
{
...
"name": "Health Care",
...
"status": "ACTIVE",
"id": 3,
...
}
The Region’s ID is 3.
When specifying a Regional Context in an event subscription, there is a naming convention of:
context.region.xx
In our scenario it will therefore be context.region.3
Ignoring the Regional Context
If your situation requires that you ignore all regional contexts for data triggers, you may set the context to context.*
4 - Identify the URL the Events Subscription should call-out to (the listener)
Identify the URL to the external webhook that the Events Subscription should POST a call-out to with a JSON payload after the event trigger has occurred. In other words for our solution, if an Employee gets created, at what address should information about the Employee be sent to, using the POST method?
Let’s assume for the example that the webhook URL is:https://webhook.site/9191f8dd-xxxxxxxxxxx
5 - Determine the values of secretHeaderName and secret
For your external webhook to accept POST call-outs from authenticated sources, must define and use values for secretHeaderName and secret.
Keep the secret confidential and we encourage its use for the verification the web-hook calls. Generate an HMAC(SHA512) of the request body using the secret as the key and compare it with the value of the of the request header “TrackTik-Subscription-Secret” or the secretHeaderName set when creating the subscription.
When defining the secretHeaderName, you can put anything you like as long as there are NoSpaces. The callout will fail and DevOps will receive notifications otherwise.
PHP example:
assert(hash_hmac('sha512', $requestBody, $secret) === $requestSecretHeaderValue);
NB: When receiving call-outs from the TrackTik Events Subscription service, the header will include the HMAC value as header key: X-HMAC-Signature
6 - Build the JSON Payload to POST the Event Subscription record
We have all that we need now. There will be additional detail to provide, but it’s just metadata strings that you’re at liberty to describe as you like. Those will be indicated in the payload sample below:
{
"customId" : "customId of your choosing like ABC-123",
"name" : "A bit more descriptive text about the purpouse of this subscription",
"events" : [
"entity:employees:created"
],
"contextFilters": {
"filters": [
{
"tag": "context.region.3",
"includeChildren": true
}
]
},
"url" : "https://webhook.site/9191f8dd-xxxxxxxxx",
"secretHeaderName" : "TrackTik-subscription-secret-without-spaces",
"secret" : "B2C60113ECB70E7....",
"failureEmail" : "(optional) An email address you'd like to monitor errors at"
}
After the event subscription has been created, you can retrieve it (including the display of secret and secretHeaderName) with:
[GET] /events-subscriptions/<id number>?include=secret,secretHeaderName
"data": {
"customId": "customId of your choosing like ABC-123",
"name": "A bit more descriptive text about the purpouse of this subscription",
"events": [
"entity:employees:created"
],
"entityResponse": null,
"contextFilters": {
"filters": [
{
"tag": "context.region.2",
"includeChildren": true
}
]
},
"userDefinedFilters": null,
"url": "https://webhook.site/9191f8dd-04c7-4d15-91df-4e7e888a2a19",
"failureEmail": "",
"lastTriggeredOn": null,
"lastTriggeredTime": null,
"id": 6,
"secret": "FBBB628016EE289...",
"secretHeaderName": "TrackTik-subscription-secret-without-spaces"
}
Crafting the fields list and adding filters
Any GET call that you build to an /entity endpoint can be replicated in the events subscription. You just need to distribute the parameters across three different arrays.
Includes
Relation lists can be pulled into the entity’s response by adding an include array in the entityResponse section:
"entityResponse": {
"include": [
"address",
"employmentProfile"
]
}
Fields list
The number of keys and their values returned from the API entity can be limited with a fields array within the entityResponse.
"entityResponse": {
"fields": [
"id",
"name",
"status"
]
}
Additional (user defined) Filters
The API’s basic filters can be added to a subscription as well as a stand-alone object list of string literals. These are outside the entityResponse because they are part of the API call made to obtain the data first in order to form the entityResponse in the callout:
These filters are the same as URL based parameters like:
?gender=F&governmentBadgeId:contains=prefixabcd etc.
"userDefinedFilters": {
"gender": "F",
"governmentBadgeId:contains": "prefixabcd",
},
Filtering Triggers by Changed Fields
For integrations that monitor changes to employee records, these can be needlessly triggered just by the act of the employee logging in because TrackTik records that changed value. This can be avoided with an additional dedicated filter for changed fields.
Integrators can filter out login activity from entity:employees:updated triggers in events subscriptions by adding this JSON object to a subscription:
"ignoreFieldsOnUpdate": [
"lastLoggedInOn"
]
It can be added at the root level, like this:
{
"customId": "EmployeeUpdate",
"name": "Employees Updated",
"events": [
"entity:employees:updated"
],
"contextFilters": {
"filters": [
{
"tag": "context.*",
"includeChildren": true
}
],
"exclusions": []
},
"userDefinedFilters": null,
==> "ignoreFieldsOnUpdate": [
"lastLoggedInOn"
],
"url": "https://etc.",
"failureEmail": "person@email.com",
"lastTriggeredOn": "2023-11-20T18:03:25+00:00",
"lastTriggeredTime": "2023-11-20T18:03:25+00:00",
"id": 10
}
This looks configurable for any number of fields, and we’re working on that, but for now we’re offering just the “lastLoggedInOn” value in the array.
Testing Events Subscriptions
To test if the ES is working, you don’t need to set up a webhook listener, but it will trigger system errors and DevOps will come and ask you about it.
You should therefore set up a listener. The site https://webhook.site/ is handy for this. Visit the site, and a URL and failure email will be generated automatically for you. Put these values into your subscription.
When defining the secretHeaderName, you can put anything you like as long as there are NoSpaces. The callout will fail and DevOps will receive notifications otherwise.
You can also look at the logs of the subscription with the endpoint path:
/events-subscriptions/{subscriptionId}/logs
If your callouts fail too many times, then the subscription will disable itself.
To re-enable the subscription for further testing, you need to:
POST /events-subscriptions/{subscriptionId}/actions/enable
And here is an example of an error notification sent via email:
Retry Mechanism
Active Events Subscriptions will attempt to make their callouts after triggers and retry upon failure following these rules (can vary a bit per client if they negotiate a different configuration with us).
Our call-outs to your webhook need to receive a 200 OK response within 3 seconds, or it will be considered failed and the retry mechanism will activate.
Our webhook delivery service have an at-least-once delivery policy as we store copies of the events across multiple servers for redundancy and high availability. On rare occasions, it's possible that they will receive duplicated events. You should should design your application to be idempotent (the application should not be affected adversely when processing the same message more than once).
| Setting | Value |
| Max retries | 10 |
| Time between retries | 120 seconds |
|
Timeout (No 2xx OK response received) |
3 seconds |
Preventing duplicate or stale suspension actions
If your webhook endpoint is unavailable or returns a non-2xx response, delivery is typically retried and may be queued for later attempts. The same event can be delivered more than once and, depending on timing, deliveries can arrive out of order. If your automation treats each delivery as a fresh command (e.g., "suspend now"), those retries can trigger repeated or unexpected suspensions — even after you've manually reactivated the subscription and corrected the webhook URL.
Key behaviors to expect
- Failed webhook deliveries are retried for a limited period with backoff. The exact schedule can vary by endpoint and load.
- The same event may be delivered multiple times until your endpoint returns a 2xx acknowledgment.
- Order is not guaranteed across retries; an older "suspend" event might arrive after a newer "reactivate" event if your endpoint was down.
- If you change your webhook URL during an incident, retries to the previous URL may continue until the retry window expires.
Make your suspension logic idempotent and ordered
- Treat webhooks as notifications, not as the source of truth for current state.
- Before applying a suspend/reactivate operation, verify the current state via the API (e.g., fetch the subscription by ID) and act only if the webhook event aligns with the latest state.
- Use event IDs and timestamps: maintain a per-subscription "last processed event timestamp/version" and ignore any event older than that checkpoint; de-duplicate by event ID so repeats do not re-trigger automation.
- Maintain a monotonic counter or version if provided in the payload; apply changes only when the incoming version is greater than your stored version.
After a manual reactivation, block stale suspend events
- Record the reactivation time/version in your system and discard any "suspend" events with an earlier timestamp/version.
- Where possible, require a state confirmation (API GET) immediately before enforcing a suspension.
Drain or neutralize backlogged retries safely
- Temporarily disable your automatic suspension workflow while your endpoint catches up, or add a time-based cutoff so you ignore events created before a specific timestamp.
- Have the endpoint return 2xx while safely ignoring events older than your cutoff, so the delivery queue is cleared without triggering actions.
-
If you need the retry queue cleared or replayed from a known checkpoint, contact Support with:
- Webhook endpoint URL(s)
- Approximate outage start/end times (with timezone)
- Example event IDs
- The cutoff timestamp or last safe event ID to resume from
Monitor and test
- Enable alerting for non-2xx webhook responses and rising retry counts.
- Log event IDs, creation timestamps, delivery attempt numbers, and your stored last-processed checkpoint per subscription.
- In a staging environment, simulate outages to confirm your filters prevent duplicate or stale suspensions.
The callouts made by Events Subscriptions are logged and can be accessed in two different methods. The first is a view of all logs sorted by most recent, and the other is to view the parent subscription and then include its log entries:
[GET] /events-subscription-logs?sort=-id
or by the ID of the
[GET] /events-subscriptions/7?include=logs
Log entries are routinely archived and then removed from the system for cold storage. Here are the time intervals:
E.g.
/events-subscriptions/9/logs?includeInactive=1
The logs will show you the subscription, the number of attempts made (up to 10 before disabling itself), the status, responseType, and lastResponse. These will help you determine the status and reasons for possible failure.
Statuses (in chronological order)
1 CREATED : The subscription has been been triggered
2 WORKING : The call-out attempts are in progress
3 COMPLETED : A call-out has been successful (200 OK)
Example of a full log entry (with failure indicators, how to troubleshoot)
NB: => prefixes have been added to some lines to help orient you to sections to inspect
{
"meta": {
"request": [
"events-subscription-logs",
"8"
],
"security": {
"granted": true,
"requested": "admin:events-subscription-logs:view",
"grantedBy": "admin:*",
"scope": null
},
"debug": null,
"resource": "events-subscription-logs"
},
"data": {
"id": 8,
"eventsSubscription": 1,
"attempts": 3,
"status": "COMPLETED",
==> "responseType": "FAILED",
==> "lastResponse": {
"code": "ERR_BAD_REQUEST",
"name": "AxiosError",
"stack": "AxiosError: Request failed with status code 404\n at settle (/var/task/node_modules/axios/dist/node/axios.cjs:1913:12)\n at Unzip.handleStreamEnd (/var/task/node_modules/axios/dist/node/axios.cjs:2995:11)\n at Unzip.emit (node:events:529:35)\n at Unzip.emit (node:domain:489:12)\n at endReadableNT (node:internal/streams/readable:1368:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:82:21)",
"config": {
"env": [],
"url": "https://webhook.site/8867c489-1127-4a29-bdce-e5886436c4a5-wrong",
"data": "{\"context\":\"context.region.2\",\"eventName\":\"entity:employees:updated\",\"entity\":{\"jobTitle\":\"Principal Applications Manager\",\"region\":2,\"employmentProfile\":781,\"id\":1857,\"customId\":\"customId1701877306\",\"firstName\":\"Catherine\",\"lastName\":\"K\",\"name\":\"Catherine K\",\"primaryPhone\":\"000-000-0000\",\"secondaryPhone\":\"000-000-0000\",\"email\":\"Catherine.K@api2679.qa.staffr.ca\",\"status\":\"ACTIVE\",\"avatar\":\"https://api2679.qa.staffr.ca/rest/v1/avatar/employees/1857/5306e7759bc04da2d5568d2e7445dc0c\",\"address\":{\"addressLine1\":\"\",\"addressLine2\":\"\",\"city\":\"\",\"country\":\"\",\"state\":\"\",\"postalCode\":\"\",\"latitude\":null,\"longitude\":null,\"id\":1564},\"updatedBy\":{\"jobTitle\":\"Support (HQ)\",\"region\":2,\"employmentProfile\":1,\"address\":2,\"id\":1000,\"customId\":\"0001\",\"firstName\":\"TrackTik\",\"lastName\":\"Support (HQ)\",\"name\":\"TrackTik Support (HQ)\",\"primaryPhone\":\"1-555-454-5606\",\"secondaryPhone\":\"\",\"email\":\"\",\"status\":\"ACTIVE\",\"avatar\":\"https://api2679.qa.staffr.ca/rest/v1/avatar/employees/1000/9b64f53592ca357adc00ef8885bf2fe3\"},\"updatedOn\":\"2023-12-06T15:41:48+00:00\"},\"arguments\":{\"resource\":\"employees\"}}",
"method": "post",
"adapter": [
"xhr",
"http"
],
"headers": {
"Accept": "application/json, text/plain, */*",
"User-Agent": "axios/1.5.1",
"Content-Type": "application/json",
"Content-Length": "1105",
"Accept-Encoding": "gzip, compress, deflate, br",
"TrackTik-Subscription-Secret": "xxxxxxxxxx"
},
"timeout": 3000,
"transitional": {
"forcedJSONParsing": true,
"silentJSONParsing": true,
"clarifyTimeoutError": false
},
"maxBodyLength": -1,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"transformRequest": [
null
],
"transformResponse": [
null
]
},
==> "status": 404,
==> "message": "Request failed with status code 404"
},
"createdBy": 1000,
"createdOn": "2023-12-06T15:41:48+00:00",
"updatedBy": 1000,
"updatedOn": "2023-12-06T15:45:53+00:00"
}
}
Call-Out Volume / Limits
The cloud infrastructure for TrackTik software is such that the subscription service is separate from the main resources of web portals, and dedicated to its task. Being separate, the subscription service can assume more call-out operations workload than the web portals themselves, which means there is no activity a web portal can generate (like creating/updating reports) that can exceed the events subscriptions mechanism.
There can be a limit to the quantity of operations just the same based on default configurations, like 1000 concurrent executions, but TrackTik’s cloud team monitors this for any approach and can scale it to 10 000 or higher for example should the need presents itself.
Model Translations (language of call-outs)
For data entities that have translations available (like report field labels), you can configure the Event Subscription to use those instead of default EN-US.
E.g. of SV-SE report field labels
GET /model-translations?id:IN=30,31
{
...
"data": [
{
"modelClass": "ReportTag",
"modelId": 1133, //report template and its name is For Translation
"fieldName": "description",
==> "language": "SV-SE",
==> "translation": "För översättning",
"keyMap": null,
"id": 30
},
{
"modelClass": "ReportTag",
"modelId": 1134, //report template's field Comment
"fieldName": "description",
==> "language": "SV-SE",
==> "translation": "Kommentarer",
"keyMap": null,
"id": 31
}
]
}
These translations can be used by the Event Subscription and included in its call-out payloads if the subscription has a language defined like:
"language": "SV-SE"If the subscription is to the /reports entity, triggered by created events, that will lead to call-outs like:
Propagation/Capture of Edits to an Events Subscription
TrackTik's Events Subscription solution is a pairing of data triggers and independent cloud-layer queues. When a trigger fires a call-out to your webhook, the configuration of the subscription is read, brought into a queue for the outgoing call, and it runs it. This running of a call-out is an independent process that is no longer looking at the subscription while it runs through its steps, including during the retry mechanism.
This means that if you edit an Events Subscription during a call-out process, those changes will NOT be noticed by the existing process. They will only be re-read and implemented in the NEXT call-out process.
E.g. If a retry mechanism is currently running and errors are being sent to one email address defined in the Events Subscription at the time of triggering, and you update that email address, the retry mechanism will not start sending errors to a different address. It will continue with its original source of truth configuration read at the time of its initial triggering.
The same is true of any other values like the data entity, the trigger, the filters/includes, webhook URL, all of it. But the next time the Events Subscription is triggered, all the new values will be read and taken up to the cloud layer queue for execution.