# How to add TimeTracking to your own CRM

> Connect any CRM, ATS or back office you built or run yourself: push workers, clients and placements in with external IDs, and pull approved hours back, with or without code.

URL: https://timetracking.tech/developers/guides/add-timetracking-to-your-crm
Last updated: 2026-10-24

This guide is for teams with a CRM, ATS or back office that is not on our [integrations list](https://timetracking.tech/developers/integrations), or that is built in-house. At the end of it:

- every worker, client and placement in your CRM exists in TimeTracking, kept in sync automatically;
- approved hours flow back to your CRM, per placement, without anyone retyping them.

Time needed: about an hour with code, or an afternoon with a no-code tool. Everything below works today on the public API; nothing has to be built on our side.

## How it fits together

| What | Lives in | Direction | How |
|---|---|---|---|
| Workers, clients, placements | Your CRM (source of truth) | CRM → TimeTracking | `PUT /…/by-external/{source}/{ref}` |
| Hours, approvals, conflicts | TimeTracking (source of truth) | TimeTracking → CRM | Webhooks + `GET /days` |
| Payroll file | TimeTracking | TimeTracking → payroll | [Export mappings](https://timetracking.tech/developers/data-out/export-mappings) |

The key idea: **your CRM's own record IDs are the link.** You never have to store TimeTracking IDs in your CRM.

## Step 1. Create an API key

1. In the TimeTracking dashboard, open **Regels & koppelingen** (Rules & integrations) → **API-sleutels** (API keys).
2. Create a key with the `write` scope. Name it after your CRM, for example `crm-sync`.
3. Store it as a secret in your CRM or integration server. It starts with `tt_live_`.

Every call below sends `Authorization: Bearer <key>` to `https://app.timetracking.tech/api/v1`. See [Authentication](https://timetracking.tech/developers/api/authentication).

## Step 2. Pick a source name

Choose one short, lowercase name for your system, for example `mycrm`. It goes in every external-ID path and keeps your IDs separate from other systems (pattern `^[a-z0-9_-]+$`).

## Step 3. Push clients, workers and placements

Call these whenever a record is created or changed in your CRM. They are upserts: the first call creates, the next calls update. Safe to repeat.

**Client** (the company the worker is placed at):

```sh
curl -X PUT "https://app.timetracking.tech/api/v1/clients/by-external/mycrm/CLIENT-77" \
  -H "Authorization: Bearer $TT_KEY" \
  -H "content-type: application/json" \
  -d '{"name": "Kade Logistics BV", "approvalMode": "office"}'
```

**Worker:**

```sh
curl -X PUT "https://app.timetracking.tech/api/v1/people/by-external/mycrm/W-12345" \
  -H "Authorization: Bearer $TT_KEY" \
  -H "content-type: application/json" \
  -d '{"employeeNumber": "1005", "firstName": "Piotr", "lastName": "Nowak", "phone": "+31612345678", "locale": "pl"}'
```

**Placement** (worker × client × period). Refer to the worker and client by **your** IDs:

```sh
curl -X PUT "https://app.timetracking.tech/api/v1/placements/by-external/mycrm/PL-555" \
  -H "Authorization: Bearer $TT_KEY" \
  -H "content-type: application/json" \
  -d '{"personExternalRef": "W-12345", "clientExternalRef": "CLIENT-77", "ref": "PL-555", "roleTitle": "Order picker", "startsOn": "2026-11-02"}'
```

Each response contains `"created": true` (new) or `false` (updated). To end a placement, send the same call with `endsOn`. To stop a worker, send `"active": false`.

Importing many records at once? Use the batch endpoints (`POST /people/batch`, `/clients/batch`, `/placements/batch`) or a one-off [CSV import](https://timetracking.tech/developers/data-in/csv-import) first, then switch to upserts for changes.

Tip: add an `Idempotency-Key` header with your CRM's change ID. A retried request then never creates a second record. See [Idempotency](https://timetracking.tech/developers/api/idempotency).

## Step 4. Get hours back into your CRM

Pick one of two patterns, or combine them.

**A. Push: webhooks (recommended).** Add your CRM's endpoint once under **Regels & koppelingen → Webhooks** in the dashboard, or by API with an `admin` key:

```sh
curl -X POST "https://app.timetracking.tech/api/v1/webhooks" \
  -H "Authorization: Bearer $TT_KEY" \
  -H "content-type: application/json" \
  -d '{"url": "https://crm.example.com/hooks/timetracking", "events": ["timesheet.approved", "timesheet.client_approved", "export.completed", "conflict.created"]}'
```

TimeTracking then sends a signed `POST` for each event. Always check the `tt-signature` header first; see [Verify webhook signatures](https://timetracking.tech/developers/webhooks/verify-signatures). The full list of events is in [Webhook events](https://timetracking.tech/developers/webhooks/events).

**B. Pull: a nightly sync.** Fetch everything that changed since your last run:

```sh
curl "https://app.timetracking.tech/api/v1/days?from=2026-11-01&to=2026-11-30&status=approved,exported&updated_since=2026-11-20T02:00:00Z" \
  -H "Authorization: Bearer $TT_KEY"
```

Each row has `placement_id`, `person_id`, `work_date`, `minutes` and `status`. Follow `next_cursor` until it is empty ([Pagination](https://timetracking.tech/developers/api/pagination)). To map a row to your CRM, read the placement once (`GET /placements/{id}`): it carries your `external_ref`.

What agencies usually write back:

| CRM field | From |
|---|---|
| Approved hours this week / month | Sum of `minutes` for `status=approved,exported` ÷ 60 |
| Last approved by client | `approved_by_client`, `approved_by_client_at` |
| Open conflicts | `GET /conflicts` or the `conflict.created` webhook |
| Exported to payroll | `export.completed` webhook |

## Step 5. Test it end to end

1. Create a test placement in your CRM and check that it appears in the TimeTracking dashboard under **Plaatsingen** (Placements).
2. Log a day of hours for that worker in the app, submit and approve it.
3. Send a test event with `POST /webhooks/{id}/test` and check what arrived with `GET /webhooks/{id}/deliveries`.
4. Confirm the hours landed in your CRM. Failed deliveries can be replayed with `POST /webhook-deliveries/{id}/replay`.

## No code? Use Zapier, Make or n8n

The same five steps work in an automation tool: a "record created/updated" trigger in your CRM → an HTTP `PUT` module for step 3, and a webhook trigger → "update record" for step 4. Step-by-step: [Zapier](https://timetracking.tech/developers/integrations/ipaas/zapier), [Make](https://timetracking.tech/developers/integrations/ipaas/make), [n8n](https://timetracking.tech/developers/integrations/ipaas/n8n).

## Let Claude build it for you

Install the [Claude Skill](https://timetracking.tech/developers/tools/claude-skill) or the [MCP server](https://timetracking.tech/developers/tools/mcp), then ask, for example: *"Write a sync that upserts our CRM placements into TimeTracking every hour and writes approved hours back to the placement record."* Claude knows the endpoints, the external-ID pattern and the safe weekly-close workflow. Paste [/llms.txt](https://timetracking.tech/llms.txt) or the [OpenAPI spec](https://app.timetracking.tech/api/v1/openapi.json) into any other AI tool for the same effect.

## Common questions

**What if a worker exists in both systems already?** An upsert only finds records it created itself (by `source` + `ref`). Linking a record that already exists in TimeTracking to your CRM ID is not available through the API yet. Start the sync before anyone enters master data by hand, or load everything once through the sync and archive the hand-made duplicates.

**Can the CRM change hours?** It can add hours as a source (`POST /claims`), but approval stays in TimeTracking. Exported and locked days never change; corrections go into an open period. See [Status lifecycle](https://timetracking.tech/developers/concepts/status-lifecycle).

**Rate limits?** 600 requests per minute per key. Use batch endpoints for bulk loads. See [Rate limits](https://timetracking.tech/developers/api/rate-limits).

**Using a known system?** There are ready-made recipes for [HubSpot](https://timetracking.tech/developers/integrations/ats-crm/hubspot), [Salesforce](https://timetracking.tech/developers/integrations/ats-crm/salesforce), [Pipedrive](https://timetracking.tech/developers/integrations/ats-crm/pipedrive), [Bullhorn](https://timetracking.tech/developers/integrations/ats-crm/bullhorn), [Carerix](https://timetracking.tech/developers/integrations/ats-crm/carerix), [Recruitee](https://timetracking.tech/developers/integrations/ats-crm/recruitee) and [OTYS](https://timetracking.tech/developers/integrations/ats-crm/otys).
