Whisper · Docs
Standards

DNS-over-HTTPS

Plain UDP/53 is unencrypted, unauthenticated, and blocked outright by half the networks an agent runs on — CI sandboxes, corporate egress proxies, serverless containers with no raw sockets.

DoH turns a DNS query into an HTTPS request, so if an agent can reach the web at all, it can resolve through Whisper's policy-aware, graph-first resolver — every answer wrapped in TLS instead of sitting in the clear for anyone on the path to read or spoof.

That matters more for agents than it ever did for browsers. An agent spawned in a container often has exactly one open path out — port 443 to a fixed set of hosts — and no /etc/resolv.conf control worth trusting. DNS-over-HTTPS (DoH), specified in RFC 8484, lets that agent send a real DNS message over the same HTTPS stack it already uses for everything else, and get back a real DNS message, confidentiality- and integrity-protected end to end.

The wire format, precisely

RFC 8484 defines DoH as HTTP carrying the standard DNS message format from RFC 1035 — not a redesigned protocol, just DNS's existing 12-byte header + question + RR sections, transported over HTTPS instead of UDP/53. Two request shapes are defined:

POST — the query is the literal, unmodified DNS wire-format message, sent as the request body:

:method = POST
:scheme = https
:authority = doh.whisper.online
:path = /dns-query
accept = application/dns-message
content-type = application/dns-message
content-length = <length of the DNS message>

<the DNS message in wire format, verbatim>

GET — the same wire-format message, base64url-encoded (RFC 4648 §5, no padding) into a single dns query parameter:

:method = GET
:scheme = https
:authority = doh.whisper.online
:path = /dns-query?dns=AAABAAABAAAAAAAAA2FjZQVhZ2VudA...
accept = application/dns-message

GET is what makes DoH cacheable by ordinary HTTP infrastructure (the response's Cache-Control / TTL-derived max-age behaves exactly like any other HTTP resource) — a shared HTTP cache in front of a resolver can shave real load off hot names without any DNS-specific logic. POST has no such caching but avoids URL-length limits on huge messages (large DNSSEC RRsets, many-name queries). Both forms are answered with content-type: application/dns-message — a standard DNS response, RCODE and all, byte-identical to what you'd get on UDP/53 except it arrived over TLS 1.2+.

There is also a JSON form some resolvers offer (application/dns-json, non-standardized, originally a Google/Cloudflare convenience format) for curl-and-jq debugging without a hex editor. Whisper's resolver speaks the RFC 8484 wire format; use the wire form for anything that has to be correct, and reach for a JSON-speaking public resolver only for throwaway shell debugging.

Why the mechanism, not just the transport

DoH is not "DNS but encrypted" as an afterthought — it's DNS as an HTTP resource, which is what makes it survive networks that plain DNS can't cross:

The Whisper resolver over DoH

Point an agent's DoH client at https://doh.whisper.online/dns-query with its X-API-Key, and every query runs through the same graph-first policy resolution whisper-ns applies on :53: the name is checked against the live security graph — geography, category, ownership, listing status — before an answer is returned; a graph opinion drives the response, and a clean name falls through to upstream DNS unmodified. Fail-open, always — if the graph is unreachable, resolution proceeds as plain, correct DNS rather than hanging or erroring, per the robustness principle this whole system is built on.

DoH is one of two ways to reach that same policy resolver — pick per your network:

Transport Auth Best for
DoH HTTPS/443, application/dns-message X-API-Key header egress-restricted networks, browsers, anything that can already do HTTPS
Dedicated :53 plain UDP/TCP 53 the resolver IP itself (a per-tenant /128) agents with open UDP egress that want zero per-query overhead

Both are Tier 2 in the connectivity tiers — "keep your own address, use Whisper for policy + control" — as opposed to Tier 1/1.5, where the agent's traffic actually egresses from a routable Whisper /128.

With stock tools

No Whisper software required — any HTTP client that can build a DNS message works. Because the Whisper resolver authenticates every DoH request — it is never an open resolver — each example carries your tenant token. The token is polymorphic in how it's presented; pick whichever channel your client can do:

Presenting the same token on two channels is fine; presenting two different tokens is rejected outright (never silently resolved with one).

kdig (from knot-dnsutils) speaks DoH natively; give it the token in the path, since kdig sets no custom HTTP header:

# kdig native DoH (+https=<path>) — token rides the URL path
$ kdig +https=/whisper_live_.../dns-query @doh.whisper.online \
       acef2002a323d40d4.demo.agents.whisper.online AAAA

;; DoH session (HTTP/2 https://doh.whisper.online:443/whisper_live_.../dns-query)
;; ANSWER SECTION:
acef2002a323d40d4.demo.agents.whisper.online. 300 IN AAAA 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4

Or build the raw wire-format message yourself and POST it with curl — the fully manual path, useful for verifying exactly what's on the wire. You don't even need a DNS library: a plain AAAA query is just the 12-byte header + one question, so a few lines of python3 stdlib hand-assemble it:

# Hand-build a minimal wire-format AAAA query (id, flags, 1 question, no EDNS)
$ python3 - <<'EOF' > query.bin
import struct
name = "acef2002a323d40d4.demo.agents.whisper.online"
qname = b"".join(struct.pack("B", len(l)) + l.encode() for l in name.split(".")) + b"\x00"
header = struct.pack(">HHHHHH", 0x1234, 0x0100, 1, 0, 0, 0)  # id, RD=1, QDCOUNT=1
question = qname + struct.pack(">HH", 28, 1)                  # QTYPE=AAAA(28), QCLASS=IN(1)
import sys; sys.stdout.buffer.write(header + question)
EOF

$ curl -s -X POST https://doh.whisper.online/dns-query \
       -H 'content-type: application/dns-message' \
       -H 'accept: application/dns-message' \
       -H 'X-API-Key: whisper_live_...' \
       --data-binary @query.bin -o answer.bin

$ xxd answer.bin | head -4   # RCODE is the low nibble of byte 3; ANCOUNT is bytes 6-7

GET form, base64url-encoding the same query with plain shell tools — here presenting the token in the path instead of a header (both authenticate identically):

$ QUERY_B64=$(base64 -w0 query.bin | tr '+/' '-_' | tr -d '=')
$ curl -s "https://doh.whisper.online/whisper_live_.../dns-query?dns=${QUERY_B64}" \
       -H 'accept: application/dns-message' -o answer.bin

With Whisper

The whisper CLI provisions an agent and hands you a key already scoped to the resolver — one command, the key shown once:

$ whisper create --register --name my-agent
whisper: created my-agent — 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
api_key: whisper_live_...        # shown once — this is your DoH credential

Under the hood that's a single control-plane call — graph.whisper.security is the same Cypher-shaped control API the CLI itself speaks:

CALL whisper.agents({op:'register', args:{label:'my-agent'}})
-> { api_key: "whisper_live_...",
     agent:   "agent-acef2002a323d40d4",
     address: "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4",
     fqdn:    "acef2002a323d40d4.demo.agents.whisper.online",
     ptr:     "acef2002a323d40d4.demo.agents.whisper.online" }

The DoH endpoint itself is a single constant — https://doh.whisper.online/dns-query — so there is nothing to allocate per agent: present the returned api_key (header, path, or host-subdomain, above) to any HTTPS-capable DoH client and you're resolving through the policy engine:

$ curl -s "https://doh.whisper.online/dns-query?dns=${QUERY_B64}" \
       -H 'accept: application/dns-message' \
       -H 'X-API-Key: whisper_live_...' -o answer.bin

No allowlisting a new IP, no opening UDP/53, because it's a hostname on 443 your egress already trusts. See Connect & egress for the full tier comparison and the WireGuard/SOCKS5 tiers that go further than "just resolve" into "actually route as the agent's own identity."

Next

Read Graph-first resolution for what happens after the DoH message lands — the policy engine that decides an answer before falling through to upstream DNS — and Connect & egress for how DoH fits alongside WireGuard and SOCKS5 as one of the four ways an agent reaches the Whisper network.