# Bring your own domain

**Your agents under your own apex. The delegation is the onboarding, and the delegation is the proof.**

## Delegation is the proof

Give your agents an identity under your own domain, not ours. You keep the name at your registrar; Whisper runs the authoritative DNS for it, signs it, and mints per-agent identities like `a{hex}.{your-domain}` that resolve to routable `/128` addresses and carry their own DANE-pinned keys.

There is no TXT challenge and no verification token to paste. Pointing your `NS` records at `ns1.whisper.online` and `ns2.whisper.online` and publishing our `DS` at your registrar is implicit enough: only the domain's holder can change the delegation at the parent, so a signed delegation observed live in the public DNS *is* the authorization. That observation is the one check in the whole system that fails closed; everything else fails open.

Everything your agents get afterwards (address, reverse DNS, key pin, RDAP object, transparency receipt) is verifiable by any third party with stock tools and no Whisper API key. So start with the proof.

## Verify it yourself

Once your domain is verified, everything below is keyless and third-party checkable, no account required. Every output here is a real capture (20 Oct 2026) against `everwatch.io`, a domain we brought ourselves, and `a4f988041b1326d8a.everwatch.io`, a real agent under it (its member name is `demo.everwatch.io`); substitute your own domain and agent name.

The delegation and its DNSSEC chain:

```sh
$ dig +norec everwatch.io NS @a0.nic.io +noall +authority
everwatch.io.		3600	IN	NS	ns1.whisper.online.
everwatch.io.		3600	IN	NS	ns2.whisper.online.
$ dig +norec everwatch.io DS @a0.nic.io +noall +answer
everwatch.io.		3600	IN	DS	61903 13 2 DC1DFA1AE92A8B25F7F74970551C85092A12C2DBC23763C00EFB8CBA AB6F6917
$ dig everwatch.io CDS @ns1.whisper.online +noall +answer
everwatch.io.		300	IN	CDS	61903 13 2 DC1DFA1AE92A8B25F7F74970551C85092A12C2DBC23763C00EFB8CBA AB6F6917
everwatch.io.		300	IN	CDS	61903 13 4 CB12BB797E49900BA508A57D161157247A451744B3F9580AEDE99E2A 9F5F8AFAF9AABE143F8070900390ACF7DC2D18AB
$ dig +tcp +dnssec @1.1.1.1 everwatch.io DS
# (header and answer shown)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25652
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
; EDNS: version: 0, flags: do; udp: 1232
everwatch.io.		3600	IN	DS	61903 13 2 DC1DFA1AE92A8B25F7F74970551C85092A12C2DBC23763C00EFB8CBA AB6F6917
everwatch.io.		3600	IN	RRSIG	DS 8 2 3600 20261110131804 20261020121804 43371 io. RdRFULo+OwvDlXgjsYjhSPhHo/yVhDrH1vXpkYT4yKLhxBK146cGRMmS eePOrmg42W5ruUM6c47TCCJT243iP+86GiUI4tsqeF2i7L+ycbdzntTq xslMJ/S/4mkGqulLbdQi7DMk/SlO4VSHiwOdetVBV2whafcG50k7tUXA 2lA=
```

The `DS` key tag (61903) at the `.io` parent matches the `DNSKEY` our servers sign with, the algorithm is 13 (ECDSA P-256 SHA-256), and the validating resolver sets the AD flag because the parent's `DS`, our `DNSKEY`, and every signature agree. The AD flag is that resolver's claim; `delv` checks the same chain on your own machine. The `CDS` set our servers publish also carries the SHA-384 digest (type 4) for registrars that accept it; the `.io` registry publishes the SHA-256 one. If you move from another DNS host, public resolvers may still hold that host's negative answers for up to its SOA minimum (3 hours at some DNS hosts), so validation can lag the DS for that long.

A minted identity under the apex:

```sh
$ dig +tcp +dnssec @1.1.1.1 a4f988041b1326d8a.everwatch.io AAAA
# (header and answer shown)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6383
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
; EDNS: version: 0, flags: do; udp: 1232
a4f988041b1326d8a.everwatch.io.	60 IN	AAAA	2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
a4f988041b1326d8a.everwatch.io.	60 IN	RRSIG	AAAA 13 3 60 20261022130000 20261020120000 61903 everwatch.io. 4ulZgfr6+O6hkOeIQJ9oRoP1I5e0sVnVxlxdZXdBHWiD+wzRWAcQgyqr LDq8yxMC3/RjIErO0X3lmFvwQDNPnQ==
$ dig +tcp +dnssec @1.1.1.1 -x 2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
# (header and answer shown)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29670
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
; EDNS: version: 0, flags: do; udp: 1232
a.8.d.6.2.3.1.b.1.4.0.8.8.9.f.4.8.e.3.2.d.6.b.9.1.0.a.2.4.0.a.2.ip6.arpa. 60 IN	PTR a4f988041b1326d8a.everwatch.io.
a.8.d.6.2.3.1.b.1.4.0.8.8.9.f.4.8.e.3.2.d.6.b.9.1.0.a.2.4.0.a.2.ip6.arpa. 60 IN	RRSIG PTR 13 34 60 20261022130000 20261020120000 58485 1.0.a.2.4.0.a.2.ip6.arpa. l1jqJptg/RDgho2fn9VwLWnNO6sRFcgc54MFcKRVmvCCgRDhdJTQnDSA L9RCHM1zGetiS7jl9DelUWer4Glx2w==
$ dig +tcp +dnssec @1.1.1.1 _443._tcp.a4f988041b1326d8a.everwatch.io TLSA
# (header and answer shown)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9605
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
; EDNS: version: 0, flags: do; udp: 1232
_443._tcp.a4f988041b1326d8a.everwatch.io. 60 IN	TLSA 3 1 1 BFD4CF2E0275244754B2F99FE0803D020D03EA2C8A0052407DAFFB55 E6748DDA
_443._tcp.a4f988041b1326d8a.everwatch.io. 60 IN	RRSIG TLSA 13 5 60 20261022130000 20261020120000 61903 everwatch.io. JWwOpuuhQK7EpTJZoUiEXifGllBK5YRy34YbwKtD64vBbxqyp5F1XcbI nrFaTJWYHtTuF+hHY3GXtcZmIxRHFA==
$ openssl s_client -connect [2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a]:443 \
    -servername a4f988041b1326d8a.everwatch.io \
    -dane_tlsa_domain a4f988041b1326d8a.everwatch.io \
    -dane_tlsa_rrdata "3 1 1 BFD4CF2E0275244754B2F99FE0803D020D03EA2C8A0052407DAFFB55E6748DDA"
# (trimmed to the verification lines)
CONNECTED(00000003)
subject=CN = a4f988041b1326d8a.everwatch.io
issuer=CN = Whisper Agent Identity Issuing CA, O = viaGraph B.V.
Verification: OK
DANE TLSA 3 1 1 ...8a0052407daffb55e6748dda matched EE certificate at depth 0
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Verify return code: 0 (ok)
$ curl -s https://rdap.whisper.online/ip/2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a \
    | jq -c '{handle, name, registrant: [.entities[] | select(.roles | index("registrant")) | .handle], up: [.links[] | select(.rel == "rdap-related-up") | .href], notice: .notices[0].title}'
{"handle":"a4f988041b1326d8a","name":"demo","registrant":["everwatch.io."],"up":["https://rdap.db.ripe.net/ip/2a04:2a01::/32"],"notice":"Published by the holder"}
$ whois -h whois.whisper.online a4f988041b1326d8a.everwatch.io | grep -E '^(inet6num|netname|agent|tenant|fqdn|status|origin|org|aggregate|source) '
inet6num    2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
netname     demo
agent       a4f988041b1326d8a
tenant      everwatch.io.
fqdn        a4f988041b1326d8a.everwatch.io
status      active
origin      AS219419
org         viaGraph B.V.
aggregate   2a04:2a01::/32
source      WHISPER
$ whois 2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a | grep -E 'Abuse contact|^(inet6num|netname|status|assignment-size|org-name):'
% Abuse contact for '2a04:2a01::/32' is 'security@whisper.security'
inet6num:       2a04:2a01::/32
netname:        WHISPER-AGENTS
status:         AGGREGATED-BY-LIR
assignment-size:128
org-name:       viaGraph b.v.
```

The AD flag is a resolver's verdict. `delv` does the validation itself: it fetches the `DNSKEY`s and checks every signature from the root trust anchor down, so neither resolver below has to be trusted. Captured 20 Oct 2026, through two unrelated public resolvers; the member name `demo.everwatch.io` resolves through its signed `CNAME` to the agent:

```sh
$ delv @1.1.1.1 demo.everwatch.io AAAA
# (RRSIG lines omitted)
; fully validated
demo.everwatch.io.	60	IN	CNAME	a4f988041b1326d8a.everwatch.io.
a4f988041b1326d8a.everwatch.io.	60 IN	AAAA	2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
$ delv @1.1.1.1 a4f988041b1326d8a.everwatch.io AAAA
# (RRSIG lines omitted)
; fully validated
a4f988041b1326d8a.everwatch.io.	60 IN	AAAA	2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
$ delv @1.1.1.1 _443._tcp.a4f988041b1326d8a.everwatch.io TLSA
# (RRSIG lines omitted)
; fully validated
_443._tcp.a4f988041b1326d8a.everwatch.io. 60 IN	TLSA 3 1 1 BFD4CF2E0275244754B2F99FE0803D020D03EA2C8A0052407DAFFB55 E6748DDA
$ delv @1.1.1.1 -x 2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
# (RRSIG lines omitted)
; fully validated
a.8.d.6.2.3.1.b.1.4.0.8.8.9.f.4.8.e.3.2.d.6.b.9.1.0.a.2.4.0.a.2.ip6.arpa. 60 IN	PTR a4f988041b1326d8a.everwatch.io.
$ delv @8.8.8.8 demo.everwatch.io AAAA
# (RRSIG lines omitted)
; fully validated
demo.everwatch.io.	60	IN	CNAME	a4f988041b1326d8a.everwatch.io.
a4f988041b1326d8a.everwatch.io.	40 IN	AAAA	2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
$ delv @8.8.8.8 a4f988041b1326d8a.everwatch.io AAAA
# (RRSIG lines omitted)
; fully validated
a4f988041b1326d8a.everwatch.io.	60 IN	AAAA	2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
$ delv @8.8.8.8 _443._tcp.a4f988041b1326d8a.everwatch.io TLSA
# (RRSIG lines omitted)
; fully validated
_443._tcp.a4f988041b1326d8a.everwatch.io. 60 IN	TLSA 3 1 1 BFD4CF2E0275244754B2F99FE0803D020D03EA2C8A0052407DAFFB55 E6748DDA
$ delv @8.8.8.8 -x 2a04:2a01:9b6d:23e8:4f98:8041:b132:6d8a
# (RRSIG lines omitted)
; fully validated
a.8.d.6.2.3.1.b.1.4.0.8.8.9.f.4.8.e.3.2.d.6.b.9.1.0.a.2.4.0.a.2.ip6.arpa. 60 IN	PTR a4f988041b1326d8a.everwatch.io.
```

Forward and reverse match. The DANE-EE pin (`3 1 1`) chains to the public root through the domain's own published `DS`, not through a CA, and the endpoint's key matches it with no CA installed; the `PTR` is signed in our reverse zone, `1.0.a.2.4.0.a.2.ip6.arpa` (tag 58485), which stays ours. The `/128` is a first-class [RDAP](/docs/rdap) object, published by us as the holder of `2a04:2a01::/32` and linking up to RIPE's record for the block; its registrant is the domain itself (under your own apex there is nothing left to hide), while a registrant on the shared `agents.whisper.online` plane is an opaque tenant handle `t<sha256>`. A raw account identifier never appears in either. A plain `whois` goes to RIPE and answers with the covering block, registered to viaGraph b.v. with our abuse contact. The mint is also logged in the append-only [transparency log](/docs/transparency).

## What the observer checks

Once your `NS` and `DS` are live at the registrar there is nothing more to do. We poll the public DNS from multiple vantage points and require all four of these to hold, agreeing across every answering vantage:

1. Your parent's `DS` RRset contains a digest matching the key we hold for your domain.
2. Every delegation `NS` points at `ns1.whisper.online` and `ns2.whisper.online`.
3. The DNSSEC chain validates: independent public resolvers (Cloudflare, Google, Quad9, over v6 and v4) return validated answers, and our own servers serve a live `DNSKEY` whose digest matches the `DS`.
4. The `NS` set and `DS` set are identical across every vantage. A split answer never wins on majority; it fails.

Only when all four hold does the domain verify and attach to your account; every identity you mint from then on lands under your apex. A timeout or a `SERVFAIL` is treated as inconclusive, never as proof of absence, so a transient blip never triggers a false revocation. A daily continuity sweep re-checks verified domains; only a definitive chain break (`NS` moved away, `DS` removed, a re-key) revokes.

## One key, two servers, one DS

The per-domain signing key is a single ECDSA P-256 CSK, algorithm 13, derived deterministically. Both authoritative servers compute a byte-identical key on their own, so they answer identically from the first second, and no private key ever crosses the wire. Re-provisioning a domain reproduces the same key, which is why the `DS` is stable enough to print on this page.

## The record set

Two record types are load-bearing: `NS` and `DS`. Our nameserver names are out of bailiwick, so no glue is needed.

```dns
; the delegation
{your-domain}.   NS   ns1.whisper.online.
{your-domain}.   NS   ns2.whisper.online.

; the delegation signer, both digest types (SHA-1 is never used)
{your-domain}.   DS   {keytag} 13 2 <SHA-256 digest>
{your-domain}.   DS   {keytag} 13 4 <SHA-384 digest>
```

For registrars that scan and auto-adopt child records (RFC 7344), the zone also serves matching `CDS` and `CDNSKEY` at the apex. It adds no `CAA`: certificates for your existing names keep renewing exactly as they did at your old provider, and a `CAA` you import or write is served as you wrote it. The trust model for an identity under a domain you bring is [DANE-EE](/docs/dane), pinned in your zone's DNSSEC chain, not WebPKI, so if you want no public CA to issue for the domain at all, write `CAA 0 issue ";"` at the apex yourself. The two `CAA` lines `op:'domain'` hands back are those choices, not records we publish for you; the second, for Whisper's own ACME issuance, is not offered for a domain you bring yet; see below.

## What each agent gets

With the domain verified, minting is unchanged; the identity simply lands under your name. Each agent under your apex carries the same surface as one under `agents.whisper.online`:

- a routable `AAAA` `/128` from `2a04:2a01::/32` (announced by AS219419),
- a reverse `PTR` back to the same name, so forward and reverse match,
- a per-agent DANE-EE `TLSA` (`3 1 1`) at `_443._tcp.<name>`, chaining through your own published `DS`,
- an [RDAP](/docs/rdap) object and [WHOIS](/docs/whois) record for the `/128`,
- inclusion in the [transparency log](/docs/transparency),
- and the rest of the standard [identity](/docs/identity) surface.

Names already minted under `agents.whisper.online` keep resolving; the two planes coexist.

## Getting onboarded

Bring your own domain is live and self-serve. `whisper.agents({op:'domain'})` is on the public control plane today, reachable with your own API key, no allowlisting and no email required:

```bash
curl -s https://graph.whisper.online/api/query \
  -H "X-API-Key: {your-api-key}" -H 'content-type: application/json' \
  -d '{"query":"CALL whisper.agents({op:\"domain\", args:{op:\"submit\", name:\"{your-domain}\"}})"}' | jq '.rows[0].result'
```

The submit response hands back everything you need to publish - the exact `NS`/`DS`/`CDS`/`CDNSKEY` set, both `CAA` lines (see below), and two operator notes (add no glue; publish the NS and DS at your parent). This is the real `result` of a submit for `everwatch.io` at 13:14Z on 20 Oct 2026, while it was still pending (its rows shown one per line):

```json
{"columns": ["kind", "value"], "rows": [
  ["state", "pending"],
  ["name", "everwatch.io"],
  ["record", "everwatch.io. 60 IN NS ns1.whisper.online."],
  ["record", "everwatch.io. 60 IN NS ns2.whisper.online."],
  ["record", "everwatch.io. 60 IN DS 61903 13 2 DC1DFA1AE92A8B25F7F74970551C85092A12C2DBC23763C00EFB8CBAAB6F6917"],
  ["record", "everwatch.io. 60 IN DS 61903 13 4 CB12BB797E49900BA508A57D161157247A451744B3F9580AEDE99E2A9F5F8AFAF9AABE143F8070900390ACF7DC2D18AB"],
  ["record", "everwatch.io. 60 IN CDS 61903 13 2 DC1DFA1AE92A8B25F7F74970551C85092A12C2DBC23763C00EFB8CBAAB6F6917"],
  ["record", "everwatch.io. 60 IN CDS 61903 13 4 CB12BB797E49900BA508A57D161157247A451744B3F9580AEDE99E2A9F5F8AFAF9AABE143F8070900390ACF7DC2D18AB"],
  ["record", "everwatch.io. 300 IN CDNSKEY 257 3 13 kW32KDAn/BJNzkDeES6MLAoH1S08mTmyyIm6k667JuO6QHcu4AdeeWfcGq2iRV1BEjd5o7G4IPIPXUMqcbRK6w=="],
  ["caa", "everwatch.io. 3600 IN CAA 0 issue \";\""],
  ["caa", "everwatch.io. 3600 IN CAA 0 issue \"whisper.online\""],
  ["note", "Do NOT add glue (A/AAAA) records for ns1.whisper.online or ns2.whisper.online. They are out-of-bailiwick of your zone, so no glue is needed - your registrar resolves them from whisper.online. Any glue you add can go stale and become a hijack/lameness vector outside our control."],
  ["note", "Publish the NS and DS records above at your registrar / parent zone. We poll your parent on a schedule; we bring your zone up as soon as we observe your parent delegating it to us, and the domain is authorized on your account automatically once the DS is live too (no further action)."]
], "sensitive": []}
```

The sub-op is named inside `args` (`op`, with `sub` accepted as an alias), and each one answers in its own shape:

- **`submit`** creates or advances the domain and returns columns `kind`/`value`, with rows `state`, `name`, `record` (the `NS`, both `DS` digests, `CDS` and `CDNSKEY`), `caa` and `note`, as above.
- **`status`** - `CALL whisper.agents({op:'domain', args:{op:'status', name:'{your-domain}'}})` - returns columns `name`/`state`/`key`/`value`, with keys `observed_ns`, `observed_ds`, `first_seen_wild`, `last_observed`, `anchor`, `parent_ds_ttl` and `served_here`, plus a `missing` row for each gap while the domain is pending. A real verified sample is on the [registry record](https://nic.whisper.online/registry#byod).
- **`list`** returns columns `name`/`state`, one row per domain on your account.

A call that names no sub-op runs `submit`; one that carries an argument `submit` does not use is refused with `400 BAD_ARGS` and writes nothing.

There's also a guard on the single-label case: `op:'domain'` on a bare label like `agent1` - not a registrable apex - fails clean instead of trying to onboard it, and points you at the right verb instead:

```json
{"ok": false, "status": 400, "error": {"code": "DOMAIN_NOT_REGISTRABLE",
  "message": "\"agent1\" is a single label, not a registrable domain. ... If you meant to give an agent a Whisper identity, use op:'register' instead."}}
```

The CLI wraps the same call - `verify` is keyless, `submit`/`status`/`list` need your key:

```bash
whisper domain verify {agent-name}.{your-domain}   # keyless - takes an agent or member name under your domain
whisper domain submit {your-domain} --json     # keyed - submits and returns the record set to publish
whisper domain status {your-domain} --json     # keyed - onboarding + trust state
```

The verification half needs no onboarding, no key, and no account: everything under *Verify it yourself* works against any verified domain.

## Withdrawing

To take the domain back, remove the `NS` and `DS` at your registrar (auto-scanning registrars can use the RFC 8078 delete sentinels the zone serves). The daily sweep observes the definitive break and revokes cleanly: the zone is withdrawn and the domain detaches from your account.

## Live vs roadmap

**Live and keyless today:** the delegated, DNSSEC-signed zone on both nameservers; deterministic per-domain keys and a stable `DS`; apex-direct names (`a{hex}.{your-domain}`); routable `/128` identities with matching reverse DNS; DANE-EE `TLSA` pins; RDAP and WHOIS; transparency-log inclusion. All of it demonstrated above, live, on `everwatch.io`.

**Live and keyed today:** self-serve onboarding through `op:'domain'` on the public control plane - `submit`, `status` and `list` are all reachable with your own key right now, and `submit` hands back the exact record set to publish, no email required.

**Not yet:** browser-trusted (WebPKI) certificates under a domain you bring. ACME issuance runs today only for apexes we operate, and there is no self-serve opt-in for a domain you bring, so `whisper domain submit --webpki` refuses and explains why rather than accepting a request nothing would act on. The second `CAA` line in the submit answer is the one that issuance will need, and the DANE-EE `TLSA` pin is the trust anchor every such identity gets by default, browser-trusted leaf or not.

## Next

- [DANE & TLSA](/docs/dane) is the key pin your own `DS` anchors
- [RDAP](/docs/rdap) is the registry object behind every `/128`
- [Verify an agent](/docs/verify) walks the full keyless proof set
