Vapi.ai ListenURL WebSocket连接报400错误,请求明确需携带的API Key
问题概述
根据Vapi.ai实时通话控制文档,通过特定通话ID可获取listenURL,但使用该URL连接WebSocket时返回400 Bad Request错误,错误详情如下:
HTTP Status: 400 Bad Request 2025/01/02 14:36:09 HTTP Headers: map[Alt-Svc:[h3=":443"; ma=86400] Cf-Cache-Status:[DYNAMIC] Cf-Ray:[8fb99766dbf554a1-DEL] Connection:[keep-alive] Content-Length:[30] Content-Type:[application/json] Date:[Thu, 02 Jan 2025 09:06:10 GMT] Server:[cloudflare] Set-Cookie:[_cfuvid=FOE3xNcNAAQ9nAbYpUexjFBnvXPCcFLTmPQwo8qzv8c-1735808770005-0.0.1.1-604800000; path=/; domain=.vapi.ai; HttpOnly; Secure; SameSite=None]] 2025/01/02 14:36:09 Error connecting to WebSocket server: websocket: bad handshake
需要明确请求头中需携带的API Key类型,以成功建立连接并处理实时通话流。
解决方案
你需要在WebSocket握手请求头中添加Vapi.ai的主API密钥,格式为:
Authorization: Bearer YOUR_VAPI_API_KEY
这个密钥可在Vapi.ai控制台生成,用于验证身份权限,确保只有合法用户能访问通话流数据。
修改后的Go代码示例
将代码中注释的授权头部分取消注释,并替换为实际的API密钥:
func LiveCallStreamingWebsocket(serverURL string) { // 设置授权请求头 header := http.Header{} header.Add("Authorization", "Bearer YOUR_VAPI_API_KEY") // 替换为你的实际API密钥 // 连接WebSocket服务器 c, resp, err := websocket.DefaultDialer.Dial(serverURL, header) if err != nil { if resp != nil { log.Printf("HTTP Status: %s", resp.Status) log.Printf("HTTP Headers: %v", resp.Header) // 建议读取响应体查看具体错误信息 body, _ := io.ReadAll(resp.Body) log.Printf("Response Body: %s", body) } log.Fatalf("Error connecting to WebSocket server: %v", err) } defer c.Close() // 处理系统中断信号,优雅关闭 interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) // 连接关闭信号通道 done := make(chan struct{}) // 存储PCM数据的缓冲区 var pcmBuffer []byte // 协程读取WebSocket消息 go func() { defer close(done) for { messageType, message, err := c.ReadMessage() if err != nil { log.Println("Error reading message:", err) return } switch messageType { case websocket.BinaryMessage: pcmBuffer = append(pcmBuffer, message...) log.Printf("Received PCM data, buffer size: %d\n", len(pcmBuffer)) case websocket.TextMessage: var msg interface{} if err := json.Unmarshal(message, &msg); err != nil { log.Println("Error unmarshalling message:", err) continue } log.Println("Received message:", msg) } } }() // 等待中断信号或连接关闭 for { select { case <-done: return case <-interrupt: log.Println("Interrupt received, closing connection...") // 发送关闭消息 err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { log.Println("Error during closing WebSocket:", err) return } // 保存PCM数据到文件 if len(pcmBuffer) > 0 { err := os.WriteFile("audio.pcm", pcmBuffer, 0644) if err != nil { log.Println("Error writing PCM data to file:", err) } else { log.Println("Audio data saved to audio.pcm") } } select { case <-done: case <-time.After(time.Second): } return } } }
额外排查提示
- 确认API密钥是否有效,未过期且具备访问通话流的权限
- 检查
Authorization头格式:Bearer与密钥之间必须有一个空格 - 打开代码中读取响应体的注释,查看服务器返回的具体错误信息,有助于快速定位问题
内容的提问来源于stack exchange,提问作者MOHD TARIK

