Skip to content

Request an integration

Tell us which system you want to connect. We review every request weekly and publish a step-by-step guide or build the connection.

Webhooks overview

How TimeTracking webhooks work: creating a subscription, the delivery request, the event envelope, latency, testing, inspecting deliveries, replay and secret rotation.

Last updated: · Markdown version

A webhook is an HTTPS POST that TimeTracking sends to your endpoint when something happens: a timesheet is submitted, a conflict appears or is resolved, a person or placement changes, an export is committed, and so on (21 event types). Use webhooks instead of polling.

Who can manage webhooks

Webhooks are managed by an admin: in the dashboard ("Regels & koppelingen" → "Webhooks"), with an API key that has the admin scope, or with an admin user access token. Read and write keys get 403 {"code":"forbidden","message":"Admin role required"} on every /webhooks endpoint. The examples below use $ADMIN_USER_TOKEN; an admin key works the same way.

Create a subscription

curl -s -X POST "$TT/webhooks" -H "Authorization: Bearer $ADMIN_USER_TOKEN" -H "content-type: application/json" \
  -d '{"url":"https://hooks.example.com/timetracking","events":["timesheet.approved","export.completed","conflict.created"]}'
{ "data": { "id": "2c799e37-907b-4d22-a297-9f4a31e7c0f4", "secret": "whsec_…" } }
  • The secret is shown once. Store it. You need it to verify signatures.
  • url must be https:// and must resolve to a public address. Loopback, private (10/8, 172.16/12, 192.168/16), link-local, CGNAT and multicast addresses are rejected, and credentials in the URL are not allowed. The check runs again before every delivery, because DNS can change.
  • events needs at least one event from the event catalogue.

List: GET /webhooks → [{id, url, events, active, created_at}]. Delete: DELETE /webhooks/{id} → {deleted: true}. A webhook can't be edited; to change its URL or events, create a new one and delete the old one.

The delivery request

POST /timetracking HTTP/1.1
Host: hooks.example.com
content-type: application/json
tt-signature: t=1792790000,v1=7e81359509a29cb02c86ac686c67bf4434826027d10933a89cee5dba3e60d62f
user-agent: TimeTracking-Webhooks/1

{"id":"evt_9087e71890de14cd0197877a","data":{"rows":2,"runId":"239a1898-fab5-486c-a2be-92d939be90cc","endsOn":"2026-12-06","mapping":"Generiek CSV (per dag)","startsOn":"2026-11-30"},"type":"export.completed","created":"2026-10-23T19:08:42.950Z"}

Envelope

Field Type Notes
id string evt_ + 24 hex Unique per event. A replay keeps the same id, so dedupe on it
type string Event name, e.g. export.completed
created ISO 8601 UTC When the event happened
data object Event-specific. Mostly IDs. Fetch details with the API

Key order in the JSON is not guaranteed. Parse it as JSON, and verify the signature over the raw bytes.

Your response

  • Return any 2xx within 10 seconds to acknowledge. Acknowledge first, process asynchronously.
  • Any 3xx, 4xx or 5xx, a timeout or a connection error counts as failed and is retried (7 attempts in total over about 21 hours). Redirects are not followed.
  • See retries for the schedule.

Latency and ordering

  • Events are queued immediately and delivered by a scheduler that runs every 5 minutes. Expect up to about 5 minutes of delay.
  • Deliveries are sent in parallel batches. Ordering is not guaranteed. Use created, and re-read state from the API when order matters.
  • Each delivery is claimed atomically, so an event is not sent twice by concurrent workers. A manual replay does send it again.

Test, inspect, replay, rotate (v1.1)

All of these need an admin key or an admin user token.

# Queue a webhook.test event for one webhook
curl -s -X POST "$TT/webhooks/$HOOK_ID/test" -H "Authorization: Bearer $ADMIN_USER_TOKEN"
# → {"data":{"queued":true}}

# Recent deliveries (newest first, limit ≤ 200, default 50)
curl -s "$TT/webhooks/$HOOK_ID/deliveries?limit=20" -H "Authorization: Bearer $ADMIN_USER_TOKEN"
{ "data": [
  { "id": 21, "event": "webhook.test", "status": "pending", "attempts": 0, "last_status_code": null, "last_error": null,
    "next_attempt_at": "2026-10-23T19:22:20.469944+00:00", "created_at": "2026-10-23T19:22:20.469944+00:00",
    "payload": { "id": "evt_78eae536b3c2ecb793b5ca22", "data": { "message": "Test event from TimeTracking" }, "type": "webhook.test", "created": "2026-10-23T19:22:20.468Z" } }
] }

status is pending (queued or waiting for a retry), delivered or failed (gave up, or the webhook is inactive).

# Re-queue one delivery (e.g. after fixing your endpoint); attempts reset to 0
curl -s -X POST "$TT/webhook-deliveries/21/replay" -H "Authorization: Bearer $ADMIN_USER_TOKEN"
# → {"data":{"requeued":true}}

# Issue a new signing secret (returned once)
curl -s -X POST "$TT/webhooks/$HOOK_ID/rotate-secret" -H "Authorization: Bearer $ADMIN_USER_TOKEN"
# → {"data":{"secret":"whsec_…"}}

Secret rotation has no overlap. Deliveries sent after the rotation are signed with the new secret only. For a seamless rotation, have your verifier accept both the old and the new secret for a short window. Rotate, deploy the new secret, then remove the old one.

Minimal receiver (Node + Express)

import express from "express";
import { verifyTimeTrackingSignature } from "./verify.js"; // see verify-signatures.md

const app = express();
const seen = new Set(); // use a database table in production

app.post("/timetracking", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verifyTimeTrackingSignature(raw, req.get("tt-signature"), process.env.TT_WEBHOOK_SECRET)) return res.sendStatus(401);
  const event = JSON.parse(raw);
  res.sendStatus(200);                     // acknowledge fast
  if (seen.has(event.id)) return;          // dedupe (replays keep the id)
  seen.add(event.id);
  queue.push(event);                       // process asynchronously, re-read details via the API
});
app.listen(3000);

No-code receivers

Zapier, Make and n8n can receive webhooks with their generic webhook trigger. Verify the signature in a code step before you trust the payload. See the Zapier, Make and n8n guides.

Next