# Any other source

**In short:** pick the row that matches where your number lives. Each way sends a count, and the first count starts the watch. If your tool can call a link, a Webhook URL is the simplest.

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}]}'
```
