Privacy
WatchMyBirds runs on your Raspberry Pi. Bird activity, images, and detections stay there unless you enable an outbound integration such as Telegram or MQTT. MQTT sends detection metadata and an image URL to your configured broker; a consumer that can reach WatchMyBirds can fetch the image.
The web interface serves scripts and styles from your installation and uses system fonts. Opening a page does not request assets from Google Fonts or a CDN. External links, such as Wikipedia and GitHub, connect to those sites when opened.
The separate optional traffic to the WatchMyBirds operator is a voluntary installation report, with a separate choice for usage and diagnostic summaries. It is off by default, and you can enable it during initial password setup or in Settings → Privacy.
Basic reports (only when you opt in)
One small JSON payload, once per UTC day, to
watchmybirds-telemetry.wmb-infra.workers.dev:
{
"installation_id": "(32 random hex chars, generated once when you opt in)",
"app_version": "v0.X.Y",
"os": "linux | darwin | windows",
"arch": "aarch64 | x86_64 | armv7l",
"cpu_count": <integer>,
"total_ram_gb": <integer, rounded to whole GB>,
"python_version": "3.x.y",
"detector_variant": "yolox-tiny-int8 | fasterrcnn | unknown"
}
These eight fields describe an installation, not a named user. The
installation_id is a random number we generate on your
device — it's not derived from your hardware, MAC,
hostname. The stable ID lets us recognize repeat reports from the same
installation; the data is pseudonymous, not fully anonymous.
Optional usage and diagnostics
A separate, initially unchecked choice enables consent version 2. Existing consent never enables it automatically. All features work when you decline.
The additional fields are schema and consent version (2), UTC report date, deployment type (container, Raspberry Pi or source), and bounded daily metrics:
- Flags for password setup completion, decoded camera frames and camera recovery, detector readiness and initialization errors, successful inference and inference errors.
- Flags for authenticated Review, correction and export use, and web server errors.
- Longest observed process uptime: under 1 hour, 1–6 hours, 6–24 hours, 1–7 days, 7–30 days, or at least 30 days.
- Slowest successful inference: under 50, 50–100, 100–250, 250–1000, 1000–5000, or at least 5000 milliseconds.
These are flags and ranges, not event counts. Missing values mean unknown. Setup completion does not prove the camera works; export use does not prove receipt or sharing. Source versions may include a Git tag/commit and a marker for local changes.
Daily summaries refresh at most hourly. A local queue holds today and up to seven previous UTC dates for retries; version and deployment describe the runtime when sent. Failed writes are retried with backoff. Turning sharing off or choosing basic reports clears pending diagnostics. Settings shows delivery status.
What the heartbeat does not include
- Your IP address, country, region, or any geo-location
- Your locale, language, or timezone
- Your hostname, MAC address, or any hardware serial
- The Raspberry Pi model string or kernel version
- Exact RAM in bytes (we round to whole GB on purpose)
- Any image, video, or audio data
- Any species names, observation counts, or detection events
- Error messages, stack traces, or debug logs
- Camera URLs, settings, passwords, or anything from your config
- Your email or any identifier from your operating system
Training-data exports
The Export page streams a ZIP of answered photos to your browser without writing an archive on this device. Each selected photo includes every object and image statement on it, manual objects, answer history and model proposal context. Sample IDs include the stable local labeling installation ID, separate from the optional heartbeat ID.
The device records the export date, answer revisions and your “Export received” confirmation date. Unconfirmed exports can be downloaded again with their original context; changed answers wait again. WatchMyBirds does not upload this bundle or send species counts to a server. Sharing requires your own manual transfer. A later optional upload would need its own explanation and per-station opt-in before implementation.
Sharing it is entirely optional and nothing in the app depends on it. The page invites you to pass your corrections on, because that is how the models everyone runs got better in the first place. Ignoring the invitation changes nothing.
If you do want to share, please don't attach the archive to a public issue — it contains your own images. Get in touch without it and we'll arrange a private transfer.
Where the data lives
The heartbeat is received by a tiny Cloudflare Worker and stored in a
Cloudflare D1 database with jurisdiction=eu, configured for EU data jurisdiction. The endpoint is
https://watchmybirds-telemetry.wmb-infra.workers.dev/v1/heartbeat.
The Worker explicitly drops the IP address, country code, and all
Cloudflare-injected location metadata before writing to the database.
The Worker source code is open and reviewable in the WatchMyBirds repo
under infra/telemetry-worker/.
The connection exposes its source IP to Cloudflare as the network provider; we do not include it in the heartbeat payload or store it in our telemetry database.
Storage and retention
Cloudflare D1 retains individual reports and diagnostics for today and the preceding 89 UTC dates. The 04:30 UTC job updates aggregate installation counts and deletes expired reports atomically. The maintainer's local reporting cache follows the same window and expires rows when it runs. Collection and cloud history do not depend on that computer being online.
Reporting queries exclude expired dates. Physical cloud deletion is delayed if the scheduled job fails; cleanup success is recorded for monitoring. Daily installation/cohort counts without IDs remain for trends; diagnostic details expire. Small cohorts can still be distinctive.
Provider recovery backups have separate retention. A restored database must run cleanup before reporting resumes. Operator-managed backups of an installation's data directory may include its diagnostic queue.
Your controls
- Off (default) — nothing is sent. Ever. The first
time you toggle telemetry on, we generate a random
installation_idand store it insettings.yamlon your device. Until then, no ID exists. - Toggle off later — pings stop immediately. Your
installation_idstays the same, so if you turn it back on, you're counted as the same install (not a new one). - Rotate ID — if you want the next opt-in to be counted as a fresh install, click the "Rotate ID" button. This wipes your current ID and generates a new one. Future reports use the new ID. Rotation does not delete previously received reports; these expire under the 90-day policy. Pending diagnostics are cleared.
- Block at the firewall — the heartbeat hostname
is deliberately separate from any other WMB endpoint, so you can
firewall-block
watchmybirds-telemetry.wmb-infra.workers.devwithout breaking anything else in the app. - Override the endpoint — set
telemetry_endpointinsettings.yamlto point at an HTTPS URL you control (HTTP is allowed only for loopback; redirects are refused) (orhttp://localhost/discard). The toggle's "on" state then sends to wherever you said.
Why we ask at all
WatchMyBirds is a small open-source project maintained by one person. GitHub stars and clones don't tell us if anyone actually runs the app — only if they bookmarked it. A daily heartbeat lets us see whether the project is being used, which informs decisions about what to fix, what to build next, and whether continuing is worth the time. Optional diagnostics helps identify setup problems, release regressions, performance issues and feature use.
Questions or concerns
If you spot something wrong, want clarification, or want to request deletion of any data: open an issue on GitHub. For a request concerning an installation, we may need its installation ID from your device to locate the relevant records. Do not post it publicly; ask for a private way to share it. Turning telemetry off stops future reports but does not automatically delete previously received data.
This page describes the behavior of the heartbeat as of the current
app version. The Worker source code in
infra/telemetry-worker/src/worker.js and the client code
in web/services/telemetry_service.py describe the collection mechanism. Please report discrepancies with this
notice so they can be corrected.