The headers
Fonoster adds these headers to the WebSocket upgrade request it sends to your stream’s URL:<url> is the stream’s URL exactly as you configured it in Fonoster, query string included.
x-fonoster-signature proves the request came from Fonoster for this URL. x-fonoster-app-signature
also binds it to one stream, so a signature made for one stream can’t open a session for another.
Check the app signature when several streams share one server and one secret.
Listen-only sockets get the same headers, signed with the same
secret.
Let the SDK check it
VoiceServer checks the signature on every connection and answers 401 Unauthorized when it
doesn’t match. It won’t start without a secret: pass it as an option or set APP_SIGNING_SECRET.
- Node.js
- Python
Pass the secret and the stream’s URL:The Node.js SDK also checks
x-fonoster-app-signature whenever the request carries it, and
Fonoster always sends it. A request with a bad app signature is refused even if the URL
signature matches.After the check,
session.signedFor holds the appRef and workspaceRef the signature
named.Set the URL when a proxy is in front
Fonoster signs the URL configured on the stream, not the URL your server sees. Behind a tunnel, load balancer or proxy, the two can differ in scheme, host or path. WithoutpublicUrl (public_url in Python), the SDK rebuilds the URL from the request’s
X-Forwarded-Host or Host header and tries both ws:// and wss://. That works when the proxy
keeps the host and path. If it rewrites either, the check fails. Set publicUrl to the configured
URL and it always matches.
Check it in your own server
The SDKs export their checks, so you can use them in a server that doesn’t useVoiceServer.
Check the headers before you accept the WebSocket.
- Node.js
- Python (FastAPI)
verifyAppSignature checks the bound signature and returns which stream it was for.
verifySignature checks x-fonoster-signature only. Both take a list of URLs, the request’s
headers and the secret, and return { ok, reason }:reason is missing (no signature headers), stale (timestamp more than 300 seconds from
your clock) or mismatch.Verify it yourself
In any other language, check the headers by hand:- Read
x-fonoster-timestampandx-fonoster-signature. Reject the request if either is missing. - Reject it if the timestamp is more than 300 seconds from your clock.
- Compute the HMAC-SHA256 of
<timestamp>.<url>with the signing secret, where<url>is the stream’s configured URL, and hex-encode it. - Compare it with
x-fonoster-signaturein constant time. Reject the request if they differ.
x-fonoster-app-ref and x-fonoster-workspace-ref, joined by newlines, and compare it with
x-fonoster-app-signature the same way.
Rotate the signing secret
Rotate the secret if it leaks, or on your regular schedule. Fonoster shows the new secret once.- Dashboard
- SDK
- CLI
- Go to Streams and open the stream.
- Open the Advanced tab.
- Under Signing secret, choose Rotate secret, then Rotate now.
- Copy the new secret from Signing secret (shown once).
APP_SIGNING_SECRET on your server and restart it.
After creation or rotation, Fonoster only shows the secret’s last four characters, for example
Current secret ends in …qWnE in the dashboard. Use them to check which secret a server has.
Troubleshooting
Next steps
Build a stream with the Voice SDK
Put your own code on a call in Node.js.
Session protocol
The full wire protocol, for clients in other languages.