AWS Lambda
A Lambda function has no fixed address — it cold-starts on whatever IP AWS hands it, so anything downstream that wants to trust "this call came from my agent" is stuck trusting a bearer token instead.
Any of the thousand execution environments behind your function's ARN can leak or replay that token just as well as the real one. Whisper gives the function a durable identity anyway: a public Lambda layer, one --layers flag, no vendoring, no Docker rebuild — and the function's traffic, not just its logs, leaves from a routable /128 a stranger can verify.
The problem with serverless identity
Every long-lived compute target — a VM, a container, a bare-metal box — earns its identity by being somewhere: it holds an IP, and that IP shows up in reverse DNS, in TLS certificates, in access logs a security team can correlate. Lambda breaks that assumption on purpose. The function has no home; AWS schedules it onto whatever execution environment is free, behind a NAT gateway or the Lambda service's own shared egress ranges, shared with every other customer's functions in that account and region. If you need the receiving system — a partner API, an internal service, a zero-trust gateway — to know which specific agent or workload made a call, you're left bolting on a bearer credential (a JWT, an API key) and hoping it never leaks, because the network layer itself gives you nothing to check it against.
Whisper's answer is the same one it gives every other compute shape: mint the function a real agent identity — forward DNS (AAAA), reverse DNS (PTR), a DANE TLSA key pin, an RDAP registration — anchored in 2a04:2a01::/32 (AS219419), and then make the function's outbound traffic actually leave from that /128. The receiving side no longer has to trust a header; it can run dig -x on the source address and get an independently verifiable answer.
The public layer — zero bundling
Lambda layers exist precisely so you don't have to pip install a dependency into every deployment zip. Whisper ships one, public, MIT-licensed:
arn:aws:lambda:eu-north-1:205639151085:layer:whisper-id-egress:1
It bundles whisper-id (the Python SDK — see Python SDK) plus requests and PySocks, all as pure-Python wheels (--implementation py --abi none --platform any), which is the whole trick: one zip serves python3.12 and python3.13, x86_64 and arm64, because there's no compiled extension to diverge per platform. Attach it and import whisper_id just works — no bundling, no rebuild when AWS ships a new runtime patch.
aws lambda update-function-configuration --function-name my-fn \
--layers arn:aws:lambda:eu-north-1:205639151085:layer:whisper-id-egress:1
Layer ARNs are region-scoped; from outside eu-north-1, copy it once with aws lambda get-layer-version-by-arn + publish-layer-version into your own region, or just vendor the three pip packages yourself — they're small and dependency-light.
Layer versions are immutable in Lambda — every rebuild publishes a new version number and needs its own add-layer-version-permission --principal '*' grant. If you pin :1 in a script, check for a newer version occasionally; the ARN's name stays whisper-id-egress forever, only the trailing version increments.
Building it yourself is one shell script and one pip install, no different from building any other pure-Python layer:
pip install --target build/python \
--implementation py --abi none --platform any --only-binary=:all: \
"whisper-id[socks]" requests
(cd build && zip -qr ../layer.zip python)
aws lambda publish-layer-version --layer-name whisper-id-egress \
--zip-file fileb://layer.zip --compatible-runtimes python3.12 python3.13 \
--compatible-architectures x86_64 arm64 --license-info MIT
Two tiers, per Postel
Every Whisper integration follows the same rule: the keyless half works for anyone, no account, no credential, so a stranger reading your function's response can get real value; the keyed half unlocks the actual control plane and egress once you supply an API key. AWS Lambda is no exception — nothing here is a demo of a capability you have to pay to unlock; both tiers are the real thing.
Tier 1 — keyless verify (no credentials at all)
whisper_id.verify() re-runs the full server-side trust chain — DANE + DNSSEC + reverse-DNS + the identity JWS — over one HTTPS GET. No CLI, no API key, works from inside any Lambda regardless of network config.
Without the SDK — in Python it's one requests call to the keyless endpoint; whisper_id.verify() is the same call, typed and exception-safe:
import requests
addr = "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"
r = requests.get(f"https://rdap.whisper.online/verify-identity/{addr}")
print(r.json()) # {"is_whisper_agent": True, "dane_ok": True, "jws_ok": True, "evidence": {...}}
The full chain behind that verdict, and how to re-derive it from a terminal, is in Verify an agent. With whisper-id, inside handler.py (attach the layer, no other setup):
import whisper_id
def handler(event, context=None):
addr = event.get("queryStringParameters", {}).get("addr")
is_agent = whisper_id.verify(addr)
return {
"statusCode": 200,
"body": {
"address": addr,
"is_whisper_agent": is_agent,
"rdap": whisper_id.rdap(addr) if is_agent else None,
},
}
aws lambda invoke --function-name whisper-egress \
--cli-binary-format raw-in-base64-out \
--payload '{"queryStringParameters":{"addr":"2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"}}' \
out.json && cat out.json
A target that isn't a Whisper agent returns a clean {"is_whisper_agent": false} — never a 500 or an exception, per the Robustness Principle.
Tier 2 — keyed egress (your traffic, your /128)
This is the part a bearer token can't give you: the function's outbound HTTP request itself leaves AWS sourced from the agent's routable /128, via the Tier-1.5 SOCKS5 / HTTP-CONNECT egress proxy described in Connect & egress. Because you reach that proxy over an https:// scheme (your TLS to the proxy, wrapping the TLS to the origin), the egress token never appears outside an encrypted channel; and if a freshly minted token's source-binding hasn't propagated across the egress fleet yet, the call is transparently relayed through the HTTPS forward gateway rather than failing.
With stock tools — once you have a connection string and an egress token, this is standard requests-over-proxy, nothing Whisper-specific:
import requests
proxies = {"https": "https://w:<egress-token>@connect.whisper.online:443"}
r = requests.get("https://v6.ident.me", proxies=proxies, timeout=20)
print(r.text) # -> 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 (the agent's own address)
With Whisper, the full handler.py two-tier recipe (from whisper-examples/lambda):
import os, json, ipaddress, urllib.request, whisper_id, requests
def source_ip():
"""Tier-1.5 egress proxy first; if the token's binding is still propagating
(or the runtime denies a raw socket), fall back one hop to the HTTPS forward
gateway. Either way the request leaves from the agent's /128."""
proxy = os.environ.get("WHISPER_EGRESS")
if proxy:
try:
return requests.get("https://v6.ident.me",
proxies={"https": proxy}, timeout=20).text.strip()
except requests.exceptions.RequestException:
pass
gw = os.environ.get("WHISPER_FORWARD_URL", "https://forward.whisper.online")
body = json.dumps({"agent": os.environ["WHISPER_AGENT"],
"url": "https://v6.ident.me"}).encode()
req = urllib.request.Request(gw + "/forward", data=body, headers={
"content-type": "application/json", "X-API-Key": os.environ["WHISPER_API_KEY"]})
return json.loads(urllib.request.urlopen(req, timeout=25).read())["body"].strip()
def handler(event, context=None):
qs = (event or {}).get("queryStringParameters") or event or {}
if qs.get("egress") is not None: # Tier 2 — keyed egress
seen = source_ip()
want = os.environ.get("WHISPER_AGENT")
return {"tier": "egress", "seen_ip": seen,
"is_whisper_agent": whisper_id.verify(seen),
"matches_agent_address": bool(want) and
ipaddress.ip_address(seen) == ipaddress.ip_address(want)}
addr = qs.get("addr") # Tier 1 — keyless verify
return {"address": addr, "is_whisper_agent": whisper_id.verify(addr)}
Deploy it with the layer attached and the egress token in the function's environment (or AWS Secrets Manager — never in code):
zip function.zip handler.py
aws lambda create-function --function-name whisper-egress \
--runtime python3.12 --architectures arm64 \
--role <your-lambda-exec-role-arn> --handler handler.handler --timeout 120 \
--layers arn:aws:lambda:eu-north-1:205639151085:layer:whisper-id-egress:1 \
--zip-file fileb://function.zip \
--environment 'Variables={WHISPER_EGRESS=https://w:<egress-token>@connect.whisper.online:443,WHISPER_FORWARD_URL=https://forward.whisper.online,WHISPER_AGENT=<your-/128>,WHISPER_API_KEY=<your-api-key>}'
aws lambda invoke --function-name whisper-egress \
--cli-binary-format raw-in-base64-out --payload '{"egress":"1"}' out.json && cat out.json
# -> {"tier":"egress","seen_ip":"2a04:2a01:...","is_whisper_agent":true,"matches_agent_address":true}
source_ip() is the whole two-tier fallback in miniature: it tries the Tier-1.5 proxy first and, if that token's source-binding is still propagating across nodes (or Lambda denies the raw socket), drops to the HTTPS forward.whisper.online/forward gateway — keyed with the agent's API key — the same relay path documented in Connect & egress and the forward gateway. Either way, the traffic leaves from the agent's /128.
Mint the agent first, from anywhere — this doesn't have to happen inside Lambda:
pip install whisper-id
whisper create --label lambda-worker # -> address (a routable /128), fqdn, api_key
Then pull the egress connection string for WHISPER_EGRESS. The CLI's whisper connect deliberately brings up a local, bearer-free proxy (socks5h://127.0.0.1:<port>) — which a Lambda can't host — so take the remote connection_string straight from the control plane instead (see Control plane):
CALL whisper.agents({op:'register', args:{label:'lambda-worker'}})
-> { agent, address, fqdn, ptr, api_key }
CALL whisper.agents({op:'connect', args:{agent:'lambda-worker', tier:'socks5'}})
-> { connection_string, http_proxy, ... } # connection_string -> WHISPER_EGRESS
Verifying it actually worked
Don't take the function's word for it — the point of address-as-identity is that anyone downstream can check independently, from a terminal, without trusting the Whisper API at all:
whisper verify --trustless 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
The seven stock-tool proofs behind that verdict (dig, openssl, whois) are in Verify an agent.
Node instead of Python
The same two-tier recipe exists for the Node 18+ runtime using the dependency-free npm whisper-edge SDK (npm i whisper-edge, zip with node_modules, set WHISPER_API_KEY for the egress tier) — see whisper-examples/lambda/handler.mjs and the broader serverless/edge write-up at Integrations.
Next: Python SDK for the full whisper_id API surface, or Connect & egress for how the SOCKS5 / HTTP-CONNECT proxy sources traffic from the agent's /128 on any platform, not just Lambda.