Skip to main content
Your stream’s URL is public, so anyone who learns it can open a WebSocket to your server. To prove a connection comes from Fonoster, Fonoster signs every connection request with the stream’s signing secret. Your server checks the signature before it accepts the connection. Every stream server must do this check. The Voice SDKs do it for you. If you run your own server, such as a Pipecat bot, add it yourself.

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.
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. Without publicUrl (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 use VoiceServer. Check the headers before you accept the WebSocket.
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:
  1. Read x-fonoster-timestamp and x-fonoster-signature. Reject the request if either is missing.
  2. Reject it if the timestamp is more than 300 seconds from your clock.
  3. Compute the HMAC-SHA256 of <timestamp>.<url> with the signing secret, where <url> is the stream’s configured URL, and hex-encode it.
  4. Compare it with x-fonoster-signature in constant time. Reject the request if they differ.
To check the app signature too, compute the HMAC-SHA256 of the timestamp, the URL, 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.
Rotation takes effect immediately, with no overlap window. From that moment, connections are signed with the new secret, and a server that still holds the old one refuses every call. Rotate during a quiet period and deploy the new secret right away.
  1. Go to Streams and open the stream.
  2. Open the Advanced tab.
  3. Under Signing secret, choose Rotate secret, then Rotate now.
  4. Copy the new secret from Signing secret (shown once).
Then set the new secret as 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.