# Tinylytics + n8n

Use [n8n](https://n8n.io) with Tinylytics through the same building blocks every other HTTP client uses: outbound [webhooks](/docs/webhooks) and the [API](/docs/api). There is no official Tinylytics n8n community node yet. You wire the built-in **Webhook**, **HTTP Request**, and **Code** nodes yourself.

## What works today

| Goal | How |
| --- | --- |
| React when a site goes down or recovers | Tinylytics webhook → n8n Webhook trigger (`monitor_down`, `monitor_up`) |
| React to live hits, kudos, or custom events | Tinylytics webhook → n8n Webhook trigger (`new_hit`, `new_kudo`, `new_event`) |
| React when an AI insight is generated | Tinylytics webhook → n8n Webhook trigger (`new_insight`) |
| React to content-monitoring issues | Tinylytics webhook → n8n Webhook trigger (`content_issue`) |
| Pull analytics on a schedule | n8n Schedule + HTTP Request against `/api/v1` |
| Verify webhook authenticity | HMAC over the raw body using `X-Signature` |

Webhooks require an **active paid subscription**. API keys are available from Account Settings → API Access. Prefer webhooks over tight polling when you can — API requests are rate limited.

## Sample workflows

Import these into n8n (**⋯** menu → **Import from File**), then follow the sticky notes in each workflow.

| Workflow | Download | What it does |
| --- | --- | --- |
| Receive webhooks | [receive-webhooks.json](/examples/n8n/receive-webhooks.json) | Verifies `X-Signature`, routes core webhook events, formats a `message` field you can send onward |
| Daily top paths | [daily-top-paths.json](/examples/n8n/daily-top-paths.json) | Calls `GET /sites/:id/hits?grouped=true&group_by=path` for yesterday and builds a short summary |
| Poll uptime | [poll-uptime.json](/examples/n8n/poll-uptime.json) | Calls `GET /sites/:id/uptime` on a timer; prefer webhooks for real downtime alerts |

The sample workflows stop at a formatted `message` (or equivalent). Connect Slack, email, Discord, Linear, or any other n8n node yourself — Tinylytics does not ship those credentials.

## 1. Receive Tinylytics webhooks in n8n

### Requirements

- Paid Tinylytics plan
- An n8n instance with a **public** HTTPS URL (Tinylytics blocks localhost and private network URLs)
- Webhook platform set to **Generic** (not Discord)
- **Wrap content** left off for the sample expressions

If you only need Discord downtime embeds, Tinylytics already has a native Discord webhook platform — you do not need n8n for that path.

### Steps

1. Import [receive-webhooks.json](/examples/n8n/receive-webhooks.json) (or recreate the flow below).
2. Open the **Tinylytics Webhook** node, activate the workflow, and copy the **Production URL**.
3. In Tinylytics go to **Account Settings → Webhooks**, create a webhook with that URL, select the event types you want, and save.
4. Copy the signing secret into the **Verify Signature** Code node (`SIGNING_SECRET`).
5. Use **Send Test** on the webhook edit screen. You should see a successful delivery and a workflow execution in n8n.

### Signature verification

Tinylytics signs the exact JSON body with your webhook signing secret and sends:

```text
X-Signature: sha256=<hex HMAC SHA-256 digest>
```

Also sent: `X-Tinylytics-Event`, `X-Tinylytics-Delivery`, `X-Tinylytics-Timestamp`.

In n8n:

1. Enable **Raw Body** on the Webhook node (required — the signature is over the raw bytes, not re-serialized JSON).
2. Verify with a Code node using the binary raw body, for example:

```js
const crypto = require('crypto');

const SIGNING_SECRET = 'your-signing-secret';
const headers = $input.item.json.headers || {};
const signature = String(headers['x-signature'] || '');
const rawBody = await this.helpers.getBinaryDataBuffer(0, 'data');
const expected = 'sha256=' + crypto.createHmac('sha256', SIGNING_SECRET).update(rawBody).digest('hex');

const sigBuf = Buffer.from(signature);
const expBuf = Buffer.from(expected);
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
  throw new Error('Invalid Tinylytics webhook signature');
}

return { json: $input.item.json.body };
```

Treat deliveries as at-least-once. Store `X-Tinylytics-Delivery`, or `hit.id` / `kudo.uid` / `analytics_event.id` / `insight.id` / `check.id`, if duplicate handling matters.

Full payload shapes are documented in [Webhooks](/docs/webhooks#payload-reference).

### Event types (core)

| Event | When it fires |
| --- | --- |
| `monitor_down` | Uptime monitor reports the site down |
| `monitor_up` | Uptime monitor reports recovery |
| `new_hit` | An accepted live hit (script, pixel, or API create/batch) |
| `new_kudo` | An accepted browser kudo, API kudo, or verified Webmention like |
| `new_event` | An accepted custom analytics event (script or API create/batch) |
| `new_insight` | A new AI insight record is created |
| `content_issue` | Content monitoring records a new/changed broken link or mixed content issue |

Ignored paths, ignored visitors, spam-suppressed writes, imports, seeds, and similar non-live writes do **not** emit hit/kudo/custom-event webhooks. See [Webhooks](/docs/webhooks).

## 2. Call the Tinylytics API from n8n

### Auth

```text
Authorization: Bearer tly-ro-your-api-key
Accept: application/json
```

Use a read-only key (`tly-ro-...`) for GET workflows. Use a full-access key (`tly-fa-...`) only when you intentionally write hits, events, kudos, signals, sites, or groups.

Create keys under **Account Settings → API Access**. Base URL: `https://tinylytics.app/api/v1`.

### Smoke test

HTTP Request node:

- Method: `GET`
- URL: `https://tinylytics.app/api/v1/me`
- Header: `Authorization: Bearer tly-ro-...`

A valid key returns HTTP `200` with your account payload.

### Useful read endpoints for automation

| Endpoint | Notes |
| --- | --- |
| `GET /me` | Validate the key |
| `GET /sites` | List sites and ids |
| `GET /sites/:id/hits` | Raw or grouped hits (`grouped=true`, `group_by=path\|country\|…`) |
| `GET /sites/:id/kudos` | Kudos list |
| `GET /sites/:id/leaderboard` | All-time path leaderboard |
| `GET /sites/:id/uptime` | Subscription + uptime enabled on the site |
| `GET /sites/:id/content` | Subscription + content monitoring |
| `GET /sites/:id/insights` | Subscription |
| `GET /sites/:id/signals` | Subscription + insights enabled on the site |

Date query params use `YYYY-MM-DD`. Analytics ranges default to UTC day boundaries; pass `time_zone=user` to use your account timezone. Details: [API docs](/docs/api).

### Example: yesterday’s top paths

```text
GET https://tinylytics.app/api/v1/sites/SITE_ID/hits
  ?grouped=true
  &group_by=path
  &start_date=YYYY-MM-DD
  &end_date=YYYY-MM-DD
  &time_zone=user
  &per_page=10
```

That is what [daily-top-paths.json](/examples/n8n/daily-top-paths.json) calls. Grouped-by-path rows include `views` (and `unique_views` when unique hits are enabled).

## What is not available

Be explicit about gaps so you do not build workflows that can never fire:

| Missing today (core product) | Workaround |
| --- | --- |
| Official Tinylytics n8n node / OAuth app | Use Webhook + HTTP Request as documented here |
| GET list endpoint for custom analytics events | Events are write-only via the API (`POST /sites/:id/events`); use the `new_event` webhook for push |
| Webhook when a scheduled email report is sent | No equivalent push event |
| Native Slack webhook platform | Send Generic webhooks to n8n, then use n8n’s Slack node |

## Troubleshooting

| Problem | What to check |
| --- | --- |
| Tinylytics rejects the webhook URL | URL must be public HTTP(S). Localhost / private IPs are blocked |
| No deliveries | Paid plan active? Webhook active? Event type selected? |
| Signature always fails | Raw Body enabled? Secret pasted correctly? Verify against raw bytes, not `JSON.stringify(body)` |
| Test works, live hits do not | Hit may be ignored, spam-suppressed, imported, or otherwise non-live — see [Webhooks](/docs/webhooks#troubleshooting) |
| API `401` | Wrong or revoked key; header must be `Authorization: Bearer …` |
| API `403` on uptime/insights/content | Active subscription required; some features must also be enabled on the site |
| Uptime poll returns `404` | Uptime is not enabled for that site |
| Discord-shaped payload in n8n | Use the **Generic** webhook platform for n8n |

## Related docs

- [Webhooks](/docs/webhooks)
- [API Reference](/docs/api)
- [Uptime Monitoring](/docs/uptime_monitoring_guide)
- [Integrations & Plugins](/docs/integrations)