# REST hooks for Zapier, Make and n8n

> Let Zapier, Make and n8n subscribe to TimeTracking events themselves: POST /hooks to subscribe, DELETE /hooks/{id} to unsubscribe, GET /hooks/samples for field mapping, with setup steps per tool.

URL: https://timetracking.tech/developers/integrations/ipaas/rest-hooks
Last updated: 2026-10-30

## 1. Summary

**Status:** Live (REST hooks API). Listed marketplace apps for Zapier and Make and a community node for n8n are still **Planned**; until then you build the trigger in each tool's developer platform or with plain HTTP steps, as described below.

**Direction:** TimeTracking to the tool (triggers). For actions (the tool writing to TimeTracking) use the REST API as in the [Zapier](https://timetracking.tech/developers/integrations/ipaas/zapier), [Make](https://timetracking.tech/developers/integrations/ipaas/make) and [n8n](https://timetracking.tech/developers/integrations/ipaas/n8n) recipes.

A REST hook is a webhook that the automation tool creates and removes itself: when a zap, scenario or workflow is switched on, the tool calls `POST /hooks` with its own receiving URL; when it is switched off, it calls `DELETE /hooks/{id}`. No one has to copy URLs or secrets between the dashboard and the tool.

## 2. Endpoints

All endpoints need an API key with the **admin** scope (dashboard *Rules & integrations* → API keys → *Admin*), because a hook sends organisation data to a URL. Use a separate key per tool.

| Call | Purpose |
|---|---|
| `POST /api/v1/hooks` | Subscribe. Body: `url` (aliases `target_url`, `hookUrl`), `event` or `events`, optional `source` (`zapier`, `make`, `n8n`, …) and `name`. Returns the subscription with `id` and the signing `secret` (once). |
| `DELETE /api/v1/hooks/{id}` | Unsubscribe (deletes the webhook). Answers `{ "deleted": true }`; 404 when it is already gone. |
| `GET /api/v1/hooks/{id}` | Does the subscription still exist? (n8n `checkExists`.) |
| `GET /api/v1/hooks` | Subscriptions made through `POST /hooks`. |
| `GET /api/v1/hooks/samples?event=…` | Example events (the exact delivered envelope) for field mapping. Without `event`: one example per event type. Any valid key. |

```sh
curl -s -X POST "$TT/hooks" -H "Authorization: Bearer $ADMIN_KEY" -H "content-type: application/json" \
  -d '{"target_url":"https://hooks.zapier.com/hooks/standard/123/abc/","event":"timesheet.submitted","source":"zapier"}'
```

```json
{ "data": { "id": "6b0f…", "url": "https://hooks.zapier.com/hooks/standard/123/abc/", "events": ["timesheet.submitted"],
            "source": "zapier", "name": "Zapier", "active": true, "created_at": "2026-10-30T09:12:01.120Z", "secret": "whsec_…" } }
```

Deliveries are ordinary [webhooks](https://timetracking.tech/developers/webhooks/overview): an HTTPS `POST` with the envelope `{ id, type, created, data }`, the `tt-signature` header, retries with backoff (7 attempts over about 21 hours), a delivery log and replay. The URL must be `https://` and resolve to a public address. An organisation can have at most 100 REST hooks. Subscriptions show on the dashboard (*Rules & integrations* → *Zapier, Make and n8n*), where an admin can delete them.

The events are listed in the [event catalogue](https://timetracking.tech/developers/webhooks/events); `GET /hooks/samples` returns the same shapes.

## 3. Zapier

In the Zapier Platform UI (or CLI) create a private integration:

1. **Authentication** → *API Key*: one field `api_key`; add the header `Authorization: Bearer {{bundle.authData.api_key}}` to every request. Test call: `GET https://app.timetracking.tech/api/v1/hooks/samples?event=webhook.test`.
2. **Trigger** → *REST Hook*, one trigger per event (for example "Hours submitted" = `timesheet.submitted`):
   - *Subscribe*: `POST https://app.timetracking.tech/api/v1/hooks`, body `{"target_url": "{{bundle.targetUrl}}", "event": "timesheet.submitted", "source": "zapier"}`.
   - *Unsubscribe*: `DELETE https://app.timetracking.tech/api/v1/hooks/{{bundle.subscribeData.data.id}}`.
   - *Perform List* (sample data and polling fallback): `GET https://app.timetracking.tech/api/v1/hooks/samples?event=timesheet.submitted`, and return `response.data` (an array with `id`).
   - *Perform*: return `[bundle.cleanedRequest]` (the delivered event).
3. Actions: *Create claim*, *Upsert person by external ID* and so on, as plain API requests (see the [Zapier recipe](https://timetracking.tech/developers/integrations/ipaas/zapier)). Send an `Idempotency-Key` header.

Without your own integration, the [Catch Raw Hook recipe](https://timetracking.tech/developers/integrations/ipaas/zapier) still works: create the webhook yourself in the dashboard.

## 4. Make

In Make's custom app editor (*My apps* → *Create a new app*):

1. **Connection**: one parameter `apiKey`; base headers `Authorization: Bearer {{connection.apiKey}}`.
2. **Webhook** (type *dedicated*, attached):
   - *Attach*: `POST https://app.timetracking.tech/api/v1/hooks`, body `{"url": "{{webhook.url}}", "events": {{parameters.events}}, "source": "make"}`, response `"data": {"externalHookId": "{{body.data.id}}"}`.
   - *Detach*: `DELETE https://app.timetracking.tech/api/v1/hooks/{{webhook.externalHookId}}`.
3. **Instant trigger** using that webhook; *Samples*: paste a result of `GET /hooks/samples?event=…`, or call it in the module's epoch/sample request.

Without a custom app: create a *Custom webhook* in a scenario, then register its URL once with `POST /hooks` (HTTP module or curl) and remove it with `DELETE /hooks/{id}` when the scenario is retired.

## 5. n8n

For a community/custom node, implement the trigger node's `webhookMethods.default`:

- `checkExists`: `GET /api/v1/hooks/{{webhookData.hookId}}` → true on 200, false on 404.
- `create`: `POST /api/v1/hooks` with `{"url": this.getNodeWebhookUrl("default"), "events": [...], "source": "n8n"}`; store `data.id` as `webhookData.hookId` and `data.secret` to verify `tt-signature` in `webhook()`.
- `delete`: `DELETE /api/v1/hooks/{{webhookData.hookId}}`.

Without a custom node: add a *Webhook* trigger node (POST, response 200), activate the workflow, and register the production URL once with an *HTTP Request* node (`POST /hooks`, header `Authorization: Bearer <admin key>`). Use `GET /hooks/samples?event=…` to pin test data. Verify the signature with a *Crypto* node as in the [n8n recipe](https://timetracking.tech/developers/integrations/ipaas/n8n).

## 6. Limits and errors

| Answer | Meaning |
|---|---|
| `403 forbidden` "Admin role required" | The key has no admin scope. |
| `422 invalid` | No `url`/`event`, an unknown event, an `http://` URL, or a URL that resolves to a private address. |
| `409 conflict` `reason: hook_limit` | 100 REST hooks in the organisation; delete unused ones. |
| `404 not_found` on `DELETE` | Already removed (treat as success). |
| `429 rate_limited` | Over the key's budget; see [rate limits](https://timetracking.tech/developers/api/rate-limits) and the `X-RateLimit-*` headers. |

Rotate a hook's secret with `POST /webhooks/{id}/rotate-secret`; replay a failed delivery with `POST /webhook-deliveries/{id}/replay`.
