Canary URLs on your own domain, and a listener you host

Short answer: a URL canary is a fake SLACK_WEBHOOK_URL or API_URL. Any request to it trips an alert. Today the dependable ways to host the URL are your own domain pointed at a canary listener, or a listener you run yourself with dynv canary listen. The hosted wildcard-IP mode (*.sslip.io, *.nip.io) is not usable yet; see the caveat below.

Token canaries (the first guide) catch someone who tries a credential against Dynv. URL canaries catch someone who calls a URL they found: a webhook URL in a leaked .env, a paste, a repository. This page covers what we tested on 2026-11-01 with a local server and the current CLI, and what we could not.

The caveat, exactly

The hosted wildcard-IP URL mode (hostnames of the form <trigger>.<ip>.sslip.io, also nip.io) additionally needs the origin to accept plain HTTP from the internet. On our current host, direct port 80 is closed because the origin accepts only Cloudflare traffic, so the hosted URL mode is not usable yet. What is usable today: token canaries, URL canaries on your own domain, DNS and database-URL canaries (per the docs; see below for what we did and didn’t run), and the self-hosted listener. Don’t rely on a *.sslip.io canary from the hosted service until this changes. Even when it works, the docs note these public wildcard-IP services are free with no SLA: if one stops resolving, its canaries stop tripping silently, and some corporate DNS filters block them.

What the URL looks like

$ dynv canary create --kind url --decoy --name SLACK_WEBHOOK_URL_ALERTS --base http://127.0.0.1:8181
created canary gbBSQVDr4SnZecrj (webhook) as SLACK_WEBHOOK_URL_ALERTS, decoy only (...)

The value is shaped like a Slack webhook: <base>/services/T<trigger>/B<random>/<random>. --shape also supports discord and api. The URL host answers only by method: POST, PUT and PATCH get 200 ok; everything else gets 404 not found, with no Dynv headers, JSON errors or CORS, so it looks like a webhook receiver, not an API. We confirmed that on a real request:

POST to the canary URL -> 200 ok
GET (a different path) -> 404 not found

Option 1: a listener you run

dynv canary listen runs a small HTTP listener that accepts URL canaries and reports each request to Dynv using a process token. Nothing in the canary URL, DNS, certificate or response points at Dynv; the requester only talks to your host.

DYNV_URL=https://api.dynv.dev DYNV_TOKEN=dynv_pt_... dynv canary listen --addr :8080
# dynv canary listen: serving on 127.0.0.1:8181   (we used --addr 127.0.0.1:8181)

Point a canary at it with --base (a host that reaches the listener). We requested the canary URL with curl and the trip appeared:

$ dynv canary trips --limit 3
2026-11-01 02:44:00Z  listener  SLACK_WEBHOOK_URL_ALERTS  127.0.0.1  ua=curl/8.9.1

The source is listener, with the requester’s user agent. For HTTPS, put your own reverse proxy and certificate in front; the docs state that “your-domain and listener modes use your own TLS.”

Option 2: your own domain

--base https://*.hooks.acme.com puts the trigger in the first host label of a domain you own, for example <trigger>.hooks.acme.com. You point a wildcard DNS record at a canary listener and that listener must accept the host. The base is stored in the canary’s sealed label, so rotation keeps using it, and the only thing linking your canaries together is your own domain, which only you know.

We tested this against a local server configured to accept any host (DYNV_CANARY_ANY_HOST=1), by sending a request with a Host header on that domain:

$ dynv canary create --kind url --decoy --name PARTNER_WEBHOOK_URL --base "https://*.hooks.acme.test"
value shape: https://<trigger>.hooks.acme.test/services/T<id>/B.../<random>
POST with Host: <trigger>.hooks.acme.test -> 200
$ dynv canary trips --limit 1
2026-11-01 02:46:03Z  http  PARTNER_WEBHOOK_URL  198.51.100.9  ua=python-requests/2.32

(Source http, with the IP supplied by our test’s trusted-proxy header.) What we did not verify: the DNS and TLS side of a real domain, and the hosted service’s address to point a wildcard CNAME at, since its hosted HTTP listener is the part blocked by the caveat above. If you run your own listener, point your wildcard record at that.

DNS and database-URL canaries

A DNS canary is a hostname (for example cache-replica-<trigger>.<zone>) that trips when resolved; a database-URL canary is a connection string such as postgres://app_ro:<password>@pg-<trigger>.<zone>:5432/app (also mysql://, redis://, mongodb://) that trips when something resolves the host, which any connection attempt does. Both need the server to have a canary DNS zone, and the docs list known noise: the lookup comes from the victim’s resolver (often a public or corporate one), resolvers cache, and security tools that pre-resolve hostnames in files can cause false positives. Our local test server had no DNS zone, so we have not run these ourselves.

Alerts, and limits

URL trips use the same alert channels, dedupe and budgets as token canaries (what the alert contains). Link-preview bots (Slackbot, Twitterbot, Googlebot) still alert, with a note that someone probably pasted the canary somewhere.

  • Your own domain and listener modes are the ones to rely on right now, and you are responsible for their availability and TLS. A listener that is down means canaries that don’t trip.
  • Plain http:// is the norm for hosted modes; the requester sees your host in the URL, so choose a hostname that looks like a real internal service.
  • A canary URL detects a request, not intent; a scanner that fetches URLs found in files will trip it.

Facts last verified . Spotted an error? Tell us.

New here? Start here for the guides in a sensible order.