Skip to main content
When you use a data connection, Ultravox connects to your WebSocket endpoint. To ensure your server only accepts connections from Ultravox, you can use custom headers for simple token-based authentication or shared secrets for HMAC-SHA256 signature verification.

Custom Headers

The simplest way to authenticate data connections is to pass headers that your server can check. Use the headers field in the data connection config to send literal key-value pairs on the outbound WebSocket connection.
Data connection with custom headers
{
  "dataConnection": {
    "websocketUrl": "wss://your-server.com/data",
    "headers": {
      "Authorization": "Bearer your-token",
      "X-Custom-Header": "custom-value"
    }
  }
}
Your server can then verify the header values before accepting the connection.

Shared Secrets

For stronger verification, shared secrets let your server cryptographically verify that a connection is from Ultravox and hasn’t been tampered with. Ultravox uses HMAC-SHA256 signatures — when shared secrets are configured, every outbound data connection WebSocket request includes three headers:
HeaderDescription
X-Ultravox-Call-IDThe UUID of the call initiating the connection.
X-Ultravox-Signature-TimestampISO 8601 UTC timestamp of when the connection was made.
X-Ultravox-SignatureHMAC-SHA256 signature(s), comma-separated if multiple secrets are configured.
The signature is computed as:
HMAC-SHA256(secret, call_id + timestamp)

Setting Shared Secrets

Shared secrets can be set at the call level or on an agent’s call template. Secrets must be between 16 and 127 characters. They are write-only and never returned in API responses.
Setting shared secrets during call creation
const response = await fetch('https://api.ultravox.ai/api/calls', {
  method: 'POST',
  headers: {
    'X-API-Key': 'your_api_key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    systemPrompt: "You are a helpful assistant...",
    model: "ultravox-v0.7",
    dataConnection: {
      websocketUrl: "wss://your-server.com/data"
    },
    sharedSecrets: ["your-secret-at-least-16-chars"]
  })
});
Setting shared secrets on an agent template
const response = await fetch('https://api.ultravox.ai/api/agents', {
  method: 'POST',
  headers: {
    'X-API-Key': 'your_api_key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: "My Agent",
    callTemplate: {
      systemPrompt: "You are a helpful assistant...",
      sharedSecrets: ["your-secret-at-least-16-chars"]
    }
  })
});
Secrets set at call creation override those from the agent template.

Verifying Signatures

When your data connection server receives an incoming WebSocket connection from Ultravox, verify it with these steps:
1

Timestamp Verification

  • The X-Ultravox-Signature-Timestamp header contains the time the connection was initiated.
  • Verify that this timestamp is recent (e.g. within the last minute) to prevent replay attacks.
2

Signature Verification

  • Ultravox signs each connection using HMAC-SHA256.
  • The signature is in the X-Ultravox-Signature header.
  • To verify:
    • Concatenate the call ID (X-Ultravox-Call-ID) with the timestamp.
    • Create an HMAC-SHA256 hash using your shared secret as the key.
    • Compare this hash with the provided signature.
Verifying Data Connection Signature
import datetime
import hmac

call_id = request.headers["X-Ultravox-Call-ID"]
timestamp = request.headers["X-Ultravox-Signature-Timestamp"]
if datetime.datetime.now(datetime.timezone.utc) - datetime.datetime.fromisoformat(timestamp) > datetime.timedelta(minutes=1):
  raise RuntimeError("Expired message")
expected_signature = hmac.new(SHARED_SECRET.encode(), (call_id + timestamp).encode(), "sha256").hexdigest()
for signature in request.headers["X-Ultravox-Signature"].split(","):
  if hmac.compare_digest(signature.strip(), expected_signature):
    break  # Valid signature
else:
  raise RuntimeError("Invalid signature")
3

Multiple Signatures

  • The X-Ultravox-Signature header may contain multiple comma-separated signatures.
  • This supports key rotation without downtime — configure both old and new secrets, then remove the old one once your server is updated.
  • Your server should check if any of the provided signatures match.