# Make recipes

> Use TimeTracking with Make (Integromat) today: custom webhooks with signature checks via sha256 HMAC, and HTTP modules for API calls.

URL: https://timetracking.tech/developers/integrations/ipaas/make
Last updated: 2026-10-24

## 1. Summary

**Status:** Recipe. A native TimeTracking app on Make is **Planned**.

**Direction:** Both. Triggers: TimeTracking webhooks. Actions: REST API calls.

## 2. Mechanism

- **Trigger:** *Webhooks → Custom webhook* with *JSON pass-through* enabled and *Get request headers* on, so you receive the raw body and the `tt-signature` header.
- **Verify:** a *Set variable* or *Filter* using Make's built-in `sha256(text; "hex"; key)` function (HMAC when a key is given).
- **Action:** *HTTP → Make a request* with `Authorization: Bearer tt_live_…`.

Assumption — verify in your Make account: option names ("JSON pass-through", "Get request headers") follow the current Make UI.

## 3. Prerequisites

- Make account; an admin user (dashboard *Regels & koppelingen*) or an API key with scope `admin` to create webhooks (`POST /webhooks` from an HTTP module works with an admin key); a `write` key for the scenario itself.

## 4. Entity mapping

| Scenario | Source | TimeTracking call |
|---|---|---|
| ATS/CRM hire → placement | candidate, company, dates | `PUT /{clients,people,placements}/by-external/{source}/{ref}` (idempotent upsert) |
| Spreadsheet/terminal hours → claims | placement id, date, minutes | `POST /claims` with `source:"import"`, `sourceRef`, client-generated `id` |
| `timesheet.approved` → ERP/billing | `data.dayIds` | `GET /days?from&to&status=approved&updated_since=…` |
| `export.completed` → store file | `data.runId`, range | `GET /exports/runs/{runId}/file` returns the exact committed CSV (read key OK). |

Upserts by external ID are full replaces. Batch endpoints (`POST /{people|clients|placements|claims}/batch`, ≤500 rows) cut operations per run.

## 5. Step-by-step setup

### Webhook trigger with verification

1. Add *Webhooks → Custom webhook*, enable JSON pass-through and request headers. Copy the URL.
2. Create the TimeTracking webhook (admin) with that URL; save the secret in a Make *Data store* or scenario variable `tt_secret`.
3. Add a *Tools → Set multiple variables* module:
   - `sig` = the `tt-signature` header value
   - `t` = `{{get(split(get(split(sig; ","); 1); "="); 2)}}`
   - `v1` = `{{get(split(get(split(sig; ","); 2); "="); 2)}}`
   - `expected` = `{{sha256(t + "." + value; "hex"; tt_secret)}}` (where `value` is the raw pass-through body)
4. Add a *Filter* after it: `expected` equals `v1`, and `{{abs(timestamp - t)}}` less than `300`.
5. *JSON → Parse JSON* on the raw body; route by `type` with a *Router*.

Make arrays are 1-based (`get(array; 1)` is the first item).

### HTTP action

*HTTP → Make a request*:

- URL `https://app.timetracking.tech/api/v1/claims`, method `POST`
- Headers `Authorization: Bearer tt_live_…`, `Idempotency-Key: make-{{1.row}}`
- Body type *Raw*, content type `application/json`:

```json
{"id":"{{uuid}}","placementId":"{{1.placement_id}}","workDate":"{{formatDate(1.date; "YYYY-MM-DD")}}","minutes":{{round(1.hours * 60)}},"source":"import","sourceRef":"make-{{1.row}}"}
```

- *Parse response* on; enable *Evaluate all states as errors* off and handle `4xx` with an error route. Store `{{uuid}}` with the source row before calling, so a re-run sends the same `id`.

## 6. Sync schedule and triggering events

Instant scenarios on webhooks (TimeTracking delivers every 5 minutes). Scheduled scenarios can poll incrementally with `updated_since` and `next_cursor` (use a *Repeater* until `next_cursor` is empty).

## 7. Corrections, conflicts and locked periods

- `409` (state does not allow, for example placement shortened before hours) and `423` (exported/locked day) are business errors: send them to a human, do not retry.
- Disagreeing sources create conflicts; resolve in TimeTracking.

## 8. Testing checklist

- [ ] Tampered secret blocks the scenario at the filter.
- [ ] Same source row run twice yields one claim (same `id`).
- [ ] Error route catches `422`/`423` and notifies.

## 9. Troubleshooting

| Problem | Fix |
|---|---|
| Hash never matches | Body was parsed and re-serialised; use JSON pass-through raw value. |
| `401` / `403` | Wrong header or read-only key. |
| `422` with `issues` | Map `issues[].path` in the error route. |
| `429` | Add *Sleep* 60 s and retry. |

## 10. Limits and known gaps

No native app or OAuth (Planned); ID-only webhook payloads; webhook management needs an admin user or `admin`-scope key.

## 11. Build it with Claude

```text
Write step-by-step Make scenario instructions (module by module, with exact formulas) that receive TimeTracking
webhooks via Custom webhook (JSON pass-through + headers), verify tt-signature with sha256(t + "." + raw; "hex";
secret) and a 300 s freshness check, then route: timesheet.approved → GET
https://app.timetracking.tech/api/v1/days?from&to&status=approved&updated_since (Bearer key, follow next_cursor) → upsert rows
in Google Sheets keyed on day id;
conflict.created → email the office. Include an error route for 409/422/423/429.
```
