Supabase Edge
Supabase Edge Functions run on Deno across a global edge network, so "just curl the target API" is a trap — no dedicated egress IP, no static address to allowlist, a different edge node every invocation.
The moment a partner API wants IP allowlisting, or you want to prove which deployment made a call, you're bolting on a third-party proxy or giving up. Whisper solves both from inside the function: verify who's calling you with zero setup, and — with one key — route the function's outbound calls through an agent's permanent, routable /128, so the far end sees a stable, DNSSEC-anchored identity instead of a rotating edge IP.
The shape: two tiers, one package
whisper-edge is a dependency-free TypeScript SDK built for exactly this class of runtime — Cloudflare Workers, Vercel, Netlify, AWS Lambda, Deno Deploy, and Supabase Edge Functions — anywhere a global fetch exists and no Node built-ins are guaranteed. It ships two tiers, Postel-shaped:
- Keyless —
verify/verifyDetails/resolve/rdap/rdapDomain. Pure HTTPS GETs againstrdap.whisper.online. No account, no key, no CLI. - Control (with a key) —
control(apiKey)wraps the single control-plane verb,CALL whisper.agents({op, args}), POSTed tograph.whisper.security. Mint agents, list them, set policy, read logs, revoke — andagentEgress(apiKey)hands you afetch-shaped object whose requests physically leave from that agent's/128.
Supabase Edge Functions run on the open-source edge-runtime (a Deno-compatible sandbox), so anything written for Deno works unmodified — including Deno.connect/Deno.startTls, which is exactly the primitive whisper-edge needs for real egress on this runtime (more on the wire mechanics below).
Keyless: verify a caller, no setup
The raw way — one fetch
A Supabase Edge function runs Deno: no dig, just fetch. The keyless verify is a single GET:
const addr = "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4";
const r = await fetch(`https://rdap.whisper.online/verify-identity?ip=${addr}`);
const { is_whisper_agent, dane_ok, jws_ok, fqdn } = await r.json();
That verdict folds the whole chain server-side — PTR, forward-confirm AAAA, the DANE-EE TLSA pin under DNSSEC (RFC 6698 + 4035), and the JWS identity document. dane_ok is the one that matters: the certificate pin traces to the IANA root with no CA in the chain. The full terminal walk is in Verify an agent.
With Whisper — a Supabase function
// supabase/functions/whisper/index.ts
// SPDX-License-Identifier: MIT
// supabase functions deploy whisper
// KEYLESS: GET /whisper?addr=<agent /128 address>
// CONTROL: GET /whisper?op=list (supabase secrets set WHISPER_API_KEY=<your key>)
import { resolve, rdap, control } from "npm:whisper-edge@^0.3.0";
Deno.serve(async (req: Request) => {
const url = new URL(req.url);
const op = url.searchParams.get("op");
if (op) {
const key = Deno.env.get("WHISPER_API_KEY");
if (!key) return Response.json({ error: "set WHISPER_API_KEY to use the control plane" }, { status: 401 });
const res = op === "list" ? await control(key).list() : await control(key).agents(op, {});
return Response.json({ op, records: res.records });
}
const addr = url.searchParams.get("addr");
if (!addr) return new Response("usage: ?addr=<agent /128 address> | ?op=list (needs a key)\n", { status: 400 });
const identity = await resolve(addr);
return Response.json({ address: addr, identity, rdap: identity ? await rdap(addr) : null });
});
supabase functions deploy whisper
supabase secrets set WHISPER_API_KEY=whisper_live_... # only needed for ?op=
curl "https://<project>.functions.supabase.co/whisper?addr=2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"
curl "https://<project>.functions.supabase.co/whisper?op=list"
resolve() returns null — never an exception — for anything that isn't a real Whisper agent, so this endpoint is safe to expose publicly as a "verify this address" utility with no auth at all.
Egress: leave from the agent's own /128
This is the part that's actually hard to DIY. A Supabase Edge Function's outbound fetch sources from whatever edge node happened to run that invocation — you cannot pin it, and it changes under you. agentEgress fixes the source address instead of chasing the PoP.
With stock tools
You can hand-roll an HTTP CONNECT tunnel over a raw TCP socket in Deno — Deno.connect() gets you the socket, Deno.startTls() upgrades it — but you'd be reimplementing the CONNECT preamble, the bearer handshake, and 407-retry timing yourself:
// the raw mechanism, for reference — this is what whisper-edge does for you
const raw = await Deno.connect({ hostname: "egress.whisper.online", port: 443 });
const tls = await Deno.startTls(raw, { hostname: "egress.whisper.online" });
const enc = new TextEncoder();
await tls.write(enc.encode(
`CONNECT api.example.com:443 HTTP/1.1\r\nHost: api.example.com\r\n` +
`Proxy-Authorization: Basic ${btoa("w:" + egressBearer)}\r\n\r\n`
));
// read the "200 Connection established" reply, then TLS-wrap `tls` again for the real request
With Whisper
import { agentEgress } from "npm:whisper-edge@^0.3.0";
const egress = await agentEgress(Deno.env.get("WHISPER_API_KEY")!);
const res = await egress.fetch("https://api.example.com/whoami");
const seen = (await res.json()).ip;
seen === egress.transport.address; // true — the request left from YOUR agent's /128
egress.close();
On Deno-family runtimes (Deno Deploy and Supabase Edge), agentEgress auto-detects and opens a Deno.connect + Deno.startTls HTTP-CONNECT tunnel to the egress proxy, source-bound to the agent's /128 via IP_FREEBIND over 2a04:2a01::/32. One caveat worth knowing before you rely on it: Deno can't layer TLS-inside-TLS, so the CONNECT preamble carrying the bearer rides the clear leg to the proxy — reflected as egress.transport.tokenProtected === false. The bearer is still short-lived, per-agent, and never logged; it just isn't double-wrapped in TLS on this specific runtime (Node gets the fully-nested form). If a fetch-only sandbox somewhere in your stack has no raw-socket API at all, the same call transparently falls back to the fetch-forward gateway (forward.whisper.online/forward) — same call site, egress.transport.tier tells you which path was actually used.
Without the SDK: proving egress with curl alone
Whatever minted the agent, you can independently prove where its traffic actually exits using only curl and the keyless echo endpoint — never trust a client-side claim:
curl -x <http_proxy_from_connect> -s https://whisper.online/egress-ip
# -> {"ip":"2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"} <- must equal the agent's own /128
Minting the agent behind the key
# stock tools — the control plane is one HTTPS POST, no SDK required
curl -s https://graph.whisper.security/api/query \
-H "X-API-Key: whisper_live_..." -H "content-type: application/json" \
-d '{"query":"CALL whisper.agents({op:'"'"'register'"'"', args:{label:'"'"'edge-fn'"'"'}})"}' | jq .
// with Whisper — same call, typed
import { control } from "npm:whisper-edge@^0.3.0";
const created = await control(Deno.env.get("WHISPER_API_KEY")!).register({ name: "edge-fn" });
// created.records[0].address -> the new routable /128 · .api_key -> shown once, store it as a secret
Reference
- SDK internals, full API surface, and the runtime-detection table (Node / Deno / Workers / fetch-only): Edge SDK (serverless)
- The mechanism
agentEgressfalls back to when a runtime has no raw socket at all: The forward gateway - DANE/TLSA verification in depth: DANE & TLSA
- Package: npm i whisper-edge · source: github.com/whisper-sec/whisper-edge (
examples/supabase)
Every distribution channel — including this one — is two-tier by construction: no key still verifies any Whisper agent against the DNSSEC root; your key unlocks the control plane and real egress. See Integrations for the full list.