Node SDK
whisper-id on npm gives a Node agent a real, routable IPv6 identity and a one-line proof of who it is — the keyless half in pure fetch, zero dependencies.
A shared NAT or cloud IP has no identity to give: every request looks like it came from your laptop, or your whole fleet from one exit. A Whisper agent gets its own /128 out of 2a04:2a01::/32 (announced by AS219419) — DNSSEC-signed, DANE-anchored, RDAP-registered — and whisper-id is the Node-runtime SDK that gets you there in one npm i.
Two tiers, one package
whisper-id follows Postel's Law end to end: liberal about who can use it (no key needed for the checks anyone should be able to run), conservative about what it exposes (the control plane only unlocks with your key, and the key never touches argv or logs).
| Tier | Needs | What you get |
|---|---|---|
| Keyless | Nothing — pure fetch |
verify, verifyDetails, rdap, egressIp |
| Control plane | WHISPER_API_KEY (pure HTTP, still no CLI) |
list, identity, agent, policy, logs, revoke |
| Egress + CLI-backed | the whisper binary on PATH |
register, egress, withEgress, ip |
npm i whisper-id
import { verify } from "whisper-id";
await verify("2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"); // → true, no key, no CLI
import { identity, withEgress } from "whisper-id"; // WHISPER_API_KEY in env
const me = await identity({ label: "my-bot" }); // allocate a routable /128
await withEgress(async (e) => {
console.log("egress on", e.proxyUrl); // curl/axios/got now leave from me.address
});
whisper-id vs whisper-edge — pick by runtime, not by preference
This is the one distinction people get wrong, so get it right up front:
whisper-id(this page) is the Node-runtime SDK. Its keyless and control-plane calls are purefetch(Node ≥18's globalfetch), butregister,egress,withEgress, andipshell out to thewhisperCLI to bring up the local egress proxy. It needs a real process and a filesystem — a long-running server, a worker, a CLI tool, a container.whisper-edgeis a dependency-free, fetch-only TypeScript SDK for runtimes that have nochild_processat all — Cloudflare Workers, Vercel Edge, Deno Deploy, Netlify Edge, Lambda, Supabase Edge Functions. It talks the same public HTTP API but never touches a binary.
If your agent runs as a plain Node/Bun process (PM2, systemd, Docker, a Kubernetes pod, a LangGraph/LangChain agent loop, an MCP server), use whisper-id. If it runs on someone else's edge runtime, use whisper-edge. Both share the same verify/verifyDetails/rdap keyless surface and the same control-plane shape — the only difference is how (or whether) egress gets wired up under the hood.
The API, in full
Keyless — no key, no CLI
import { verify, verifyDetails, rdap, egressIp } from "whisper-id";
await verify(addr); // boolean — server-side DANE + DNSSEC + reverse-DNS + JWS
await verifyDetails(addr); // { is_whisper_agent, fqdn, operator, dane_ok, jws_ok, ... } | null
await rdap(addr); // the public RDAP object for the /128, or null
await egressIp(); // the IP this process currently leaves from
Under the hood verify/verifyDetails hit the same keyless gateway you can curl directly (see below) — verify-identity/<addr> on rdap.whisper.online. rdap(addr) fetches the RDAP document for the /128, an RFC 9082/9083-shaped JSON ip network object, the same one whois-style tooling resolves via RFC 7484 bootstrap.
Control plane — with a key, still no CLI
Every call here sends your key only in the X-API-Key request header (never argv, never a log line) to graph.whisper.security, and throws a WhisperError (with .status/.retryAfter) on failure instead of returning undefined:
import { list, identity, agent, policy, logs, revoke } from "whisper-id";
// WHISPER_API_KEY=whisper_live_... in the environment
await policy({ default: "deny", allow: ["api.openai.com", "*.githubusercontent.com"] });
for (const a of await list()) console.log(a.address, a.label);
const recent = await logs({ kind: "dns", limit: 100 });
await revoke(me.agent); // irreversible — needs the admin:dns scope
identity({ label, contact_email }) is the one-call path to a routable /128: it returns { agent, address, fqdn, ptr, state } where fqdn looks like acef2002a323d40d4.<tenant>.agents.whisper.online and ptr is the matching ip6.arpa delegation — both live in authoritative DNS the moment the call returns.
Egress + CLI-backed
import { register, egress, withEgress, ip } from "whisper-id";
const a = await register("checkout-bot", { newKey: true }); // mints agent + its own API key
const e = await egress({ agent: a, tier: "wireguard" }); // routed /128, sets proxy env
console.log(await ip()); // → a.address
e.close(); // restores the environment
egress() sets HTTP_PROXY/HTTPS_PROXY/ALL_PROXY for you, so any proxy-env-aware client — curl, axios, got, most CLI tools spawned from your process — leaves from the /128 with zero further code. Node's own global fetch (undici) does not read those env vars, so pass a dispatcher explicitly:
import { egress } from "whisper-id";
import { ProxyAgent } from "undici";
const e = await egress();
const res = await fetch("https://api64.ipify.org", { dispatcher: new ProxyAgent(e.proxyUrl) });
console.log(await res.text()); // ← your /128, not your host's
Verifying an agent — raw fetch vs the typed SDK
Verification is keyless. In Node the "raw" way is a single fetch; the SDK is the same call, typed and null-safe:
// raw — one fetch, no package
const r = await fetch("https://rdap.whisper.online/verify-identity/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4");
const { is_whisper_agent, dane_ok, jws_ok } = await r.json();
// with whisper-id — typed, and returns null (not a throw) for a non-agent
import { verifyDetails } from "whisper-id";
const v = await verifyDetails("2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4");
console.log(v?.is_whisper_agent, v?.dane_ok, v?.jws_ok); // true true true
Both ask Whisper's server to run the chain. To re-derive it yourself from the DNSSEC root, trusting no server, that's a terminal job (dig, openssl) or whisper verify --trustless — see Verify an agent and DNSSEC.
Requirements
Zero runtime dependencies. Keyless checks and the control plane need only Node ≥18 (global fetch) and, for the control plane, WHISPER_API_KEY in the environment or { apiKey } on each call. Only register, egress, withEgress, and ip need the whisper binary on PATH:
curl -fsSL https://get.whisper.online | sh
$WHISPER_BIN overrides the CLI path; $WHISPER_CONTROL_URL / $WHISPER_RDAP_URL override the endpoints for self-hosted or staging setups. Source: github.com/whisper-sec/whisper-node, MIT licensed. Every other language and runtime combination — Python (pip install whisper-id), edge/serverless, n8n, MCP — is catalogued on /docs/integrations.
Next: whisper-edge for fetch-only edge runtimes · whisper CLI for the trustless verification chain and local egress daemon.