Whisper · Docs
Serverless & edge

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:

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

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.

Next: Edge SDK (serverless) · Vercel / Netlify / Deno