# CountWatch recipes

One page per source. Each ends with the same three checks.

- [Stripe payments (Webhook URL)](https://countwatch.com/docs/recipes/stripe.md): Count successful charges with a Stripe webhook to a Webhook URL; import 89 days of charges in 5-minute windows.
- [Supabase orders (pg_cron + pg_net)](https://countwatch.com/docs/recipes/supabase.md): A scheduled count query in the database sends 5-minute order counts every 5 minutes; the same count, sent from the database too, imports the last 89 days.
- [GitHub activity (Webhook URL)](https://countwatch.com/docs/recipes/github.md): Count pushes, pull requests or deployments with a repository webhook to a Webhook URL.
- [Count in your app (Node or Python)](https://countwatch.com/docs/recipes/app-counter.md): A copy-paste counter: count events in the process, send every 5-minute window with zeros every 5 minutes.
- [n8n workflows (Webhook URL)](https://countwatch.com/docs/recipes/n8n.md): Count a workflow's items or runs with one HTTP Request node that calls a Webhook URL.
- [Base44 backend functions](https://countwatch.com/docs/recipes/base44.md): Count each call of your Base44 functions, ok or error, with a small wrapper and a Webhook URL; alerts to a function.

## Not verified yet

These are not run end to end in CI yet; follow them with care.

- [Website traffic (Cloudflare bridge)](https://countwatch.com/docs/recipes/cloudflare-traffic.md): A small Worker in your own Cloudflare account reads the zone's visitor traffic at the account's send interval (every 5 minutes today) and sends it; the first run imports 30 days.
- [Shopify orders (Webhook URL)](https://countwatch.com/docs/recipes/shopify.md): Count new orders with a Shopify webhook to a Webhook URL; import past orders from the orders export.
- [Zapier and Make (Webhook URL)](https://countwatch.com/docs/recipes/zapier-make.md): Count a Zap's or scenario's runs with one HTTP step that calls a Webhook URL.
- [Segment events (Webhook URL)](https://countwatch.com/docs/recipes/segment.md): Count one Segment event (Order Completed) with a Webhooks (Actions) destination or a Destination Function that calls a Webhook URL.
- [RudderStack events (Webhook URL)](https://countwatch.com/docs/recipes/rudderstack.md): Count one RudderStack event (Order Completed) with a Webhook destination and a small transformation that calls a Webhook URL.
- [PostHog events (Webhook URL)](https://countwatch.com/docs/recipes/posthog.md): Count one PostHog event (Order Completed) with an HTTP Webhook destination in Data pipelines that calls a Webhook URL.
- [PostHog events (scheduled query)](https://countwatch.com/docs/recipes/posthog-query.md): A scheduled job counts one PostHog event per 5-minute window with PostHog's Query API and sends the counts; the same script imports 89 days.
- [Watch a Tinybird endpoint](https://countwatch.com/docs/methods/tinybird.md): Publish the 5-minute count query as a Tinybird endpoint; a cron job reads it and sends it; a second endpoint imports 89 days.

## No recipe for your source

Use a recipe when one fits. Otherwise use one of these general methods: every source fits one, and none needs a new endpoint.

| Your source | Method |
|---|---|
| A count you can query: a SQL database (Postgres, MySQL, MariaDB), a warehouse or log store (ClickHouse, Tinybird), another service's API | **Scheduled query → ingest.** A cron job or a CI schedule runs every 5 minutes. It counts per 5-minute window over the last 2 hours, **every 5-minute window, zeros included** (`generate_series` in SQL, `WITH FILL` in ClickHouse), up to the last complete one, and sends them with `set` to an **explicit** watch: a re-send replaces a 5-minute window (never doubles it), late rows are picked up, and a stopped job raises the no-data alert. Written out and run in CI: [Watch a ClickHouse query](https://countwatch.com/docs/methods/clickhouse.md), [Watch a Postgres query](https://countwatch.com/docs/methods/postgres.md). |
| Your own code sees each event (an app, a worker, a job) | **Client library:** `npm install countwatch` (Node 20.19+, Bun, Deno), then `import { CountWatch } from "countwatch"; const cw = new CountWatch();`, or `pip install countwatch` (Python 3.9+), then `from countwatch import CountWatch; cw = CountWatch()`; then `cw.count("orders")` where the event happens. It reads `COUNTWATCH_API_KEY` and defaults to https://api.countwatch.com; for this host set `COUNTWATCH_API_URL=https://countwatch.com`. No package manager? Paste the counter file from the [app-counter recipe](https://countwatch.com/docs/recipes/app-counter.md): the same `count()` calls, sent every 5 minutes. Each sums per 5 minutes, sends a 0 for each empty 5-minute window of every metric it has seen (an explicit watch: a stopped process raises the no-data alert), sends at the account's interval (`send_interval_seconds` in `GET /v1/status`, read again after each send) and retries without double counts. Declare metrics at start (`metrics`) to get zeros before their first event. Serverless (no timer): call `flush()` before returning; the watch is then a push watch. |
| A tool that sends webhooks or can call a URL (forms, no-code tools, CI) | **Webhook URL:** made in the dashboard (Senders (Connect), https://countwatch.com/dashboard/connections?tab=senders) and pasted into the tool; each hit adds 1 (or `?n=`); a delivery id (`?id=` or the sender's delivery header) counts a retry once. It makes a **push** watch: the link is called as things happen, so a 5-minute window with no hits is zero. |
| A shell script or cron job that finishes something often (every few minutes or more) | **Webhook URL with curl** at the end of the script: `curl -fsS "$COUNTWATCH_WEBHOOK_URL"` (a push watch; `npx countwatch send <metric> [n] --tag key:value` comes with the npm package and sends with `COUNTWATCH_API_KEY` instead). A push watch raises the no-data alert only with `expected_interval_minutes` set (`PATCH /v1/watches/{watch_id}` with an admin key, the `watch_id` from `GET /v1/status`; over MCP `manage update_watch`): set it a little above the job's normal gap. |
| Past data: the scheduled query over the last 89 days, or an export file (CSV) | **Import once:** count per 5 minutes, every 5-minute window (an empty one is a `0`), with UTC start times, and send it with `POST /v1/import` (`resolution: "5m"`; the API takes up to 90 days back, and the examples take 89 days so the first 5-minute window is still inside the 90 days when the request arrives), ending where the scheduled sends begin. The API stops an import at a watch's first live 5-minute window, so it never overwrites live counts (the add-0 point that creates a push watch does not count). `"1h"` or `"1d"` only for a source that keeps nothing finer (they are spread evenly over 5-minute windows). Only the times of single events (sign-up or order timestamps)? Send a `watches` entry `{"metric": …, "events": ["2026-08-01T19:04:44Z", …]}` instead of counts (up to 10,000 per entry): each adds 1 to its 5-minute window, and the empty 5-minute windows up to now are filled with `0`. |

Rules for any method:

- Create the watch first, with its source: `POST /v1/watches` (MCP `manage create_watch`) with `"source"` push or explicit makes it before any data (until its first count `PATCH /v1/watches/{watch_id}` `{"source": …}` still changes it; after it the source is fixed); otherwise an explicit watch by its first `set` points, a push watch by one point `{"metric": "…", "add": 0}` with `"source": "push"`.
- One metric per thing counted (`orders`, `signups`); tags only for a few values each (every combination is a watch, within the plan's watch limit).
- Batch scheduled sends: one request carries up to 10,000 points.
- A sender that sends every 5 minutes uses about 8,640 requests a month: Free's 30,000 cover 3 such senders; an e-mail warns at 80%.
- Keys and Webhook URLs are secrets. Key: run `npx countwatch init` in the project; the person approves it in the browser and it is saved as COUNTWATCH_API_KEY (`--print` pipes it to a host's secrets). Never read, print or ask for a key, a signing secret, a Webhook URL or a Slack URL in chat. A Webhook URL (`COUNTWATCH_WEBHOOK_URL`, under Senders (Connect)) goes into tools that call a URL: an agent gets it only through MCP `webhook_url` (the person approves) to paste into the tool itself. Code reads those names. Keys can also be made under Settings (https://countwatch.com/dashboard/settings).
- Then the same checks as every recipe: `GET /v1/status` (an explicit watch's `missing_buckets_24h` stays 0), the import's `status: done`, a test alert.

The scheduled query's request (`ts` is each 5-minute window's start, ISO 8601 or Unix milliseconds, at most 6 hours back):

```
curl -sX POST https://countwatch.com/v1/ingest -H "Authorization: Bearer $COUNTWATCH_API_KEY" -H "Content-Type: application/json" \
  -d '{"recipe":"scheduled-query","points":[{"metric":"orders","ts":"<bucket start>","set":7},{"metric":"orders","ts":"<next bucket start>","set":0}]}'
```
