Set up webhooks

Set up webhooks

This guide covers how to create, retrieve, and delete webhook subscriptions, validate HMAC signatures, and handle webhook expiration.

Before you begin

  • Obtain a valid access token. See Authenticate.
  • Required scopes: connector-protimeapi-webhooks.read and connector-protimeapi-webhooks.write.
  • Your destination endpoint must be reachable over HTTPS and able to return a success status code.
  • See Webhook lifecycle for background on event delivery, retries, and expiration.

Create a webhook

Create a webhook by sending a request to the webhooks endpoint with the destination URL and collection name.

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

Host: <tenant>.myprotime.eu
Authorization: Bearer eyJ...Uc
Content-Type: application/json
User-Agent: YourService/v1 (YourCompany)
{
  "destinationUrl": "https://www.your-company.com/protime/clockings",
  "collectionName": "clockings"
}

A successful response returns 201 Created. The Location header contains the webhook URI and the response body includes a private webhook key.

Save the private webhook key from the creation response. You need it to validate HMAC signatures. The key is not retrievable after creation.

Retrieve a list of webhooks

Retrieve your webhooks with optional filters on collection-name or status.

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

Host: <tenant>.myprotime.eu
Authorization: Bearer eyJ...Uc
{
  "value": [
    {
      "id": "a7d853ea-89eb-4735-83d1-b891c6fde398",
      "validUntil": "2025-03-31T12:30:00+02:00",
      "status": "Enabled",
      "destinationUrl": "https://www.your-company.com/protime/clockings",
      "collectionName": "clockings"
    }
  ]
}

Filter by status:

GET /connector/protimeapi/api/v1/webhooks?filter=status%20eq%20'Disabled' HTTP/1.1
Host: <tenant>.myprotime.eu

Retrieve a webhook by ID

GET /connector/protimeapi/api/v1/webhooks/{id} HTTP/1.1
Host: <tenant>.myprotime.eu
Authorization: Bearer eyJ...Uc

Delete a webhook

Soft-delete a webhook to stop receiving events. You can still query debugging endpoints after deletion.

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

Host: <tenant>.myprotime.eu
Authorization: Bearer eyJ...Uc

Validate the HMAC signature

Every webhook event includes an Authorization header with an HMAC-SHA256 signature. Validate it to confirm the request came from Protime and that the payload was not tampered with.

The header format is: Authorization: HMAC-SHA256 {signature}

Example validation 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;
}
Compute the signature from the raw JSON request body. Do not deserialize and re-serialize the payload first – serialization differences will produce a different hash.

Handle webhook expiration

A webhook becomes Disabled when its validUntil is reached or when all delivery retries are exhausted. Treat both cases identically – the renewal procedure is the same.

Monitor the validUntil property (returned at creation and on any GET) so you know when expiration is imminent, and monitor the status property to detect disablement caused by delivery failures.

Renewal procedure

  1. Re-synchronize the data using the collection’s GET list endpoints (or a fresh delta initial request) to cover the period since the webhook was last delivering events. Events from that period are not replayed by Protime, so this step fills the gap.
  2. POST to /webhooks with the same destinationUrl and collectionName as the original subscription. The system re-enables the existing subscription rather than creating a duplicate.
  3. Store the new private webhook key returned in the response. It immediately replaces the old key for HMAC signature validation – the old key stops matching inbound signatures the moment the new one is issued.
  4. Store the new validUntil returned in the response and use it to schedule the next renewal.

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

{
  "destinationUrl": "https://www.your-company.com/protime/clockings",
  "collectionName": "clockings"
}
The private webhook key returned at renewal is new. HMAC validation must switch to the new key as part of the renewal flow. Continuing to validate against the previous key will reject every subsequent event.
The destinationUrl + collectionName pair is the durable identifier for renewal. The webhook id may change. Do not rely on the original id to address the renewed subscription.

Add external references to webhooks

Include the externalReferences query parameter when creating the webhook. The webhook events will then include the specified external references in the payload.

Predefined external reference:

POST /connector/protimeapi/api/v1/webhooks?externalReferences=(people,@badge-number) HTTP/1.1
Host: <tenant>.myprotime.eu
Content-Type: application/json
{
  "destinationUrl": "https://www.your-company.com/protime/clockings",
  "collectionName": "clockings"
}

Custom external reference:

POST /connector/protimeapi/api/v1/webhooks?externalReferences=(activity-definitions,actDef) HTTP/1.1
Host: <tenant>.myprotime.eu
Content-Type: application/json
{
  "destinationUrl": "https://www.your-company.com/protime/clockings",
  "collectionName": "clockings"
}
External references cannot be added, updated, or removed on an existing webhook. Delete the webhook and create a new one with the desired references.

Handle duplicate events

Some actions in Protime trigger multiple webhook events due to automatic calculations. Use the changeVersion property on each event to determine whether a received object is newer than one you already processed. If the changeVersion is older (string comparison), ignore it.

Related