Webhooks

Webhooks

This page documents the webhook specification for the Protime API, including supported collections, properties, HMAC signature format, automatic retry intervals, expiration behavior, and management endpoints.

Supported collections

The following collections support webhooks:

Collection collectionName
Absences absences
Absence definitions absence-definitions
Absence groups absence-groups
Access clockings access-clockings
Activity durations activity-durations
Assignments assignments
Break definitions break-definitions
Breaks breaks
Calculated totals calculated-totals
Clockings clockings
Contracts contracts
Counters counters
Counter definitions counter-definitions
Counter groups counter-groups
Paid presences paid-presences
People people
Shift definitions shift-definitions
Work interruptions work-interruptions

Webhook 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: Enabled or 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.

Status values

Status Description
Enabled The webhook is active and delivering events. This is the initial status after creation.
Disabled The webhook has been disabled (e.g., due to expiration or prolonged unreachability).

Webhook expiration and renewal

Each webhook has a validity period exposed via the validUntil property (ISO 8601 timestamp), set when the webhook is created. There is no dedicated “refresh” or “touch” endpoint, but the validity period can be extended by re-POSTing to /webhooks with the same destinationUrl and collectionName (see Renewing a webhook). This works whether the existing webhook is still Enabled or has already become Disabled.

When a webhook becomes Disabled

A webhook transitions to the Disabled state when either:

  • validUntil is reached, or
  • all retries are exhausted after repeated delivery failures (see Automatic retries).

Once Disabled, event delivery stops and any events that were still being retried are purged.

Renewing a webhook

A webhook is renewed by POSTing to /webhooks again with the same destinationUrl and collectionName as the original subscription. The system updates the existing subscription rather than creating a duplicate, and the response contains:

  • A new private webhook key that replaces the old one. Subsequent HMAC validations must use the new key; the previous key immediately stops matching inbound signatures.
  • A new validUntil value, extending the validity period.

The existing subscription is updated in place, so the webhook keeps the same id across renewals. The destinationUrl + collectionName pair is what identifies the subscription to renew.

Renewal behaves differently depending on the current status:

  • Enabled (proactive renewal). Re-POSTing before validUntil is reached pushes the expiration forward in place. Event delivery is never interrupted, so no re-sync is needed. The external references in the renewal request must match those of the existing webhook; otherwise the request is rejected. External references cannot be changed this way – delete and recreate the webhook instead.
  • Disabled (recovery). Re-POSTing re-enables a webhook that expired or was disabled after repeated delivery failures. Because events were dropped while it was disabled, a re-sync is required first (see below).

Mandatory pre-renewal re-sync (Disabled only)

Events that occurred while the webhook was disabled are not replayed when the subscription is re-enabled. The renewed webhook only delivers events that occur after the renewal call.

Before renewing a Disabled webhook, perform a full data re-synchronization using the collection’s GET list endpoints (or a fresh delta initial request). This fills the gap between the last successful delivery and the renewal. See Handle webhook expiration for the step-by-step procedure.

changeVersion field

Each object in a webhook event includes a changeVersion field. This field supports string comparison for determining relative ordering. If a newly received object has a changeVersion older than a previously received object with the same identifier, the newer receipt can be ignored.

Some actions in the Protime environment can trigger multiple webhook events due to automatic calculations. The changeVersion field is the mechanism for handling duplicates and ordering.

Event payload format

Each webhook event is delivered as a JSON object with two fields:

Field Type Description
changeType string The type of change: InsertOrUpdate or Delete.
data object The affected resource, serialized in the same shape as the corresponding collection endpoint.

Example:

{
    "changeType": "InsertOrUpdate",
    "data": {
        "id": "d4f7a5c1-0e82-4b3a-9c6f-1a2b3c4d5e6f",
        "changeVersion": "00000000000000012345"
    }
}

The data object contains all fields of the affected resource, matching the shape returned by the corresponding collection GET endpoint.

The data object includes a changeVersion field that supports ordering and deduplication (see changeVersion field).

HMAC-SHA256 signature

Every webhook request includes an HMAC signature in the Authorization header:

Authorization: HMAC-SHA256 {signature}

The signature is generated using:

  • Algorithm: HMAC-SHA256
  • Key: The private webhook key returned when the webhook is created
  • Input: The exact JSON request body
  • Encoding: The result is Base64-encoded

The Authorization header always indicates the authentication scheme used (HMAC-SHA256), allowing future scheme upgrades.

Validation example (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;
}
The signature must be calculated on the exact JSON request body as received. Binding the incoming request to a custom model and re-serializing it is not reliable, because serialization settings may differ.

Automatic retries

Webhooks contain a built-in retry mechanism. The default retry intervals are:

Retry Interval after previous attempt Approximate total elapsed time
1 ~1 hour ~1 hour
2 ~3 hours ~4 hours
3 ~8 hours ~12 hours
4 ~24 hours ~36 hours
5 ~36 hours ~72 hours

The total retry window is up to 72 hours.

If the webhook consumer fails to respond with a success status code after all retries, automatic retries stop and the webhook is disabled. All events still in a retry state at that moment are purged.

Occasional duplicate delivery is possible. Every event includes a changeVersion that enables idempotent processing.

Management endpoints

Create a webhook

POST
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks

Request body:

{
    "destinationUrl": "https://www.fictional-customer.com/protime/clockings",
    "collectionName": "{collectionName}"
}

On success, the response includes:

  • Status: 201 Created
  • Location header: /connector/protimeapi/api/v1/webhooks/{id}
  • Response body: Contains the private webhook key (store it for HMAC validation).

Retrieve a list of webhooks

GET
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?filter=<filter-expression>

Response:

{
    "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"
        }
    ]
}

Filters

Property Operator Example
collection-name in filter=collection-name in ('activity-durations','clockings')
status eq filter=status eq 'Disabled'

Retrieve a webhook by ID

GET
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks/{Id}

Delete a webhook

DELETE
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks/{Id}

Performs a soft delete. Returns 204 No Content on success. The webhook remains queryable via debugging endpoints after deletion.

External references with webhooks

Adding, updating, or deleting external references on an existing webhook is not possible. To change external references, delete the existing webhook and create a new one with the desired references.

Predefined external references

POST
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?externalReferences=(people,@badge-number)

{
    "destinationUrl": "https://www.fictional-customer.com/protime/clockings",
    "collectionName": "clockings"
}

Custom external references

POST
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/webhooks?externalReferences=(activity-definitions,actDef)

{
    "destinationUrl": "https://www.fictional-customer.com/protime/clockings",
    "collectionName": "clockings"
}

See also