Webhooks
General information
As a webhook-consumer, you are able to subscribe to change-events in the Protime API. When changes are made to items of a certain collection, an event is raised and the API sends the changes via an HTTP POST request to the subscribed webhook-consumers. This way, you can receive the latest changes from the Protime API.
Tip
Check the Swagger page for more technical information on the endpoints.
Maintenance
Caution
- A webhook is only valid for a limited time after creation. Check the
validUntilproperty in the response. - When the destination is unreachable for an extended period of time, the webhook will be disabled. more info about retries
- When the webhook is disabled, expiring or expired, create a new one to get the new changes.
- In the response, each object (e.g., Clocking object) includes a changeVersion field. This field is crucial for determining whether a newly received object is more recent than a previously received one. If the changeVersion of a newly received object is older than the changeVersion of a previously received object with the same unique identifier, the new object can be ignored. Note that the changeVersion field supports string comparison for determining its relative value.
What to do when a webhook expires?
When a webhook reaches its expiration date (see the validUntil property) or gets disabled due to prolonged unreachability, it is mandatory to fully reinitialize the process:
-
Re-initiate the initial sync
Start by performing a full initialization again using the relevant endpoints. This ensures your data is in sync with the current state. -
Create a new webhook
After the initialization, register a new webhook by calling the Create a webhook endpoint with the appropriate destination URL and collection name.
Caution
This is the only way to guarantee you won’t miss changes between the moment the webhook expired and the creation of a new one. Reusing an expired or disabled webhook is not supported.
Multiple webhooks due to calculations
Note
Some actions in our environments can trigger multiple webhook events due to automatic calculations.
How to handle this?
- Use the
changeVersionproperty in webhook events (Check Maintenance).
This behavior is by design and ensures that your system receives all relevant updates as they are calculated in the Protime environment.
Supported collections
The following collections currently support webhooks in the Protime API:
| Collection | collectionName |
|---|---|
| Absences | absences |
| Absence definitions | absence-definitions |
| Access clockings | access-clockings |
| Activity durations | activity-durations |
| Calculated totals | calculated-totals |
| Clockings | clockings |
| Contracts | contracts |
| Counters | counters |
| Counter definitions | counter-definitions |
| Paid presences | paid-presences |
| People | people |
| Shift definitions | shift-definitions |
You can subscribe to any of these collections by specifying the collectionName in your webhook creation request.
Properties
| Property | Type | Description |
|---|---|---|
id |
string | Unique identifier for the webhook. |
validUntil |
string | Expiration date and time of the webhook (ISO 8601 format). |
status |
string | Status of the webhook (Uninitialized, Enabled, Disabled). |
externalReferences |
object | External references used for the webhook (e.g., people, terminals). |
destinationUrl |
string | Destination URL for webhook events towards the client. |
collectionName |
string | Name of the collection subscribed to for webhook events. |
External references
More information about external references can be found on the External references page.
Warning
Adding, updating or deleting the external references is not possible for an existing webhook-consumer. If you want to change the external references, you need to delete the existing webhook-consumer and create a new one with the desired external references.
Add a webhook with predefined external references:
Example:
POST https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?externalReferences=(people,@badge-number)
{
"destinationUrl": "https://www.fictional-customer.com/protime/clockings",
"collectionName": "clockings"
}Add a webhook with custom external references:
Example:
POST https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?externalReferences=(activity-definitions,actDef)
{
"destinationUrl": "https://www.fictional-customer.com/protime/clockings",
"collectionName": "clockings"
}Endpoints
Webhooks come with a management API, allowing you to manage the webhooks completely on your own without the involvement of a consultant.
Create a webhook
To be able to receive the latest changes from the Protime API, you need to create a webhook. Add the desired destination url and collection to the POST request.
When the webhook was created successfully, the id can be found in the ’location’ header.
Headers:
location = /connector/protimeapi/api/v1/webhooks/{id}
Tip
Don’t forget to save the private webhook key returned in the response. This key is crucial if you want to validate the Authorization header later on.
Example:
POST https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks
{
"destinationUrl": "https://www.fictional-customer.com/protime/clockings",
"collectionName": "{collectionName}"
}
// Replace collectionName with one of the supported collectionsDelete a webhook
If a webhook subscription is no longer needed, you can soft-delete it. You will still be able to “debug” the webhook by calling debugging endpoints
Example:
DELETE https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks/{Id}
Retrieve a list of webhooks
This endpoint will return the details of a specific webhook with id. It is mainly useful for debugging purposes.
Example:
GET https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?filter=<filter-expression>
{
"value": [
{
"id": "a7d853ea-89eb-4735-83d1-b891c6fde398",
"validUntil": "2025-03-31T12:30:00+02:00",
"status": "Enabled",
"destinationUrl": "https://www.fictional-customer.com/protime/clockings",
"collectionName": "clockings"
},
// ...
],
"NextLink": null
}Filters
It is possible to filter webhooks on the following properties:
Collection name
Example of a request with collection name filter
GET https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?filter=collection-name in ('activity-durations','clockings')
Status (Enabled,Disabled)
Example of a request with status filter
GET https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?filter=status eq 'Disabled'
Retrieve a webhook by id
This endpoint will return the details of a specific webhook with id.
GET https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks/{Id}
HMAC-signature
Requests to the webhook-consumers will always include an HMAC-signature in an Authorization header. This signature gets generated based on a combination of the event-content, private webhook key and HMAC-SHA-256 algorithm. This header is a security best practice for webhooks and provides data-integrity for the webhook-consumer.
Headers:
Authorization = HMAC-SHA256 {signature}
The header allows you to validate:
- that it is actually Protime that sent the request instead of an external attacker
- that no one tampered the data of the event that is being transmitted
Tip
It is entirely your responsibility and highly recommended that the webhook-consumer validates the Authorization header.
How to validate the Authorization header:
Example in C#:
private async Task<bool> ProtimeAuthHeaderIsValid(AuthenticationHeaderValue protimeAuthHeaderValue)
{
if (protimeAuthHeaderValue.Scheme == "HMAC-SHA256")
{
var requestBody = await GetJsonRequestBody();
const string webhookKey = "privateWebhookKey";
using var hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(webhookKey));
var bytes = Encoding.UTF8.GetBytes(requestBody);
var hash = hmacSha256.ComputeHash(bytes);
var calculatedHmacSignature = Convert.ToBase64String(hash);
return protimeAuthHeaderValue.Parameter == calculatedHmacSignature;
}
return false;
}(this can be done in different programming languages too)
Tip
- Calculate the signature based on the exact JSON-request that we provided to you. It is not possible to first bind the incoming request to a model of your own and compare it with the serialization of that model. This is not a fault-proof method because serialization settings can differ.
- The Authorization header will always indicate the authentication scheme that was used to calculate the HMAC-signature for that specific request. This allows us to use more advanced authentication schemes in the future when required. It is recommended that webhook-consumers check the used authentication scheme and act accordingly.
Automatic retries
Webhooks contain a built-in retry mechanism that prevents data-loss for the webhook-consumer in case of unexpected issues.
The default retry intervals are as follows:
- +/- 1 hour after the original attempt
- 3 hours after the first retry (i.e., approx. 4 hours after original attempt)
- 8 hours after the second retry (i.e., approx. 12 hours after original attempt)
- 24 hours after the third retry (i.e., approx. 36 hours after original attempt)
- 36 hours after the fourth retry (i.e., approx. 72 hours after original attempt)
This means there is a retry window of up to 72 hours in total.
If a webhook-consumer fails to respond with a success status code after a fair amount of retries we will stop performing automatic retries and disable the webhook-consumer. All events that are still in a “retry-state” at that moment will be purged.
Warning
It is the consumer’s responsibility to restore the correct state on your side.
This can be achieved by using the existing endpoints in the Protime API. You can re-enable your webhook again with a call to the POST webhook endpoint.
The flow is built to limit the amount of duplicate requests to a webhook-consumer but occasionally it is possible that you receive the same event twice (or more). Events will always contain a ChangeVersion that enable the webhook-consumer to deal with this in an idempotent way.