Events Subscriptions (aka Call-outs / Webhooks)

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)

Events Subscriptions Flow.drawio.png

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.

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": [ "employee", "employee.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.

Notice how you can use the dot member naming convention (employee.id) to target fields inside of included relation lists in the example below.

"entityResponse": { "fields": [ "id", "locked", "status", "vacant" ] }

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:

?locked=false&vacant=false&payable=true etc.

"userDefinedFilters": { "locked": false, "vacant": false, "payable": true, "employee.employmentProfile.payRateType": "HOURLY", "employee.employmentProfile.employeeType": "EMPLOYEE" },

 

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:

image-20221117-204020.png

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):

Setting

Value

Max retries

10

Time between retries

120 seconds

Timeout (no response received to callout)

3 seconds

HTTP Status Codes (that signal OK, otherwise it’s a failure that will lead to a retry)

2xx

Logging

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:

Logs are archived after 1 week. These can be accessed by adding the includeInactive=1 filter:
E.g.
/events-subscriptions/9/logs?includeInactive=1
Logs are moved from the system into cold storage after 3 months and are no longer accessible via API. You’ll need support from TrackTik to access these.

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

image-20231206-162000.png

2 WORKING : The call-out attempts are in progress

image-20231206-162103.png

3 COMPLETED : A call-out has been successful (200 OK)

image-20231206-162311.png

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.

 

 

 

 

Was this article helpful?
0 out of 0 found this helpful

Articles in this section