Code Examples
Production-ready examples for common Voicebip integrations. Every example includes HMAC-SHA256 signature verification using the X-Voicebip-Signature header to ensure webhook authenticity.
Express.js BYOM Webhook Handler
A Node.js Express server that receives call.transcription events, verifies the HMAC signature, and returns a BYOM response.
1 const express = require("express"); 2 const crypto = require("crypto"); 3 4 const app = express(); 5 const SIGNING_SECRET = process.env.VOICEBIP_SIGNING_SECRET; 6 7 // Parse raw body for HMAC verification 8 app.use(express.json({ 9 verify: (req, _res, buf) => { req.rawBody = buf; } 10 })); 11 12 function verifySignature(req) { 13 const signature = req.headers["x-voicebip-signature"]; 14 const timestamp = req.headers["x-voicebip-timestamp"]; 15 if (!signature || !timestamp) return false; 16 17 // Reject replays older than 5 minutes 18 if (Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)) > 300) return false; 19 20 // Signed payload is "{timestamp}.{body}" 21 const signedPayload = Buffer.concat([ 22 Buffer.from(timestamp + "."), 23 req.rawBody, 24 ]); 25 const expected = "sha256=" + crypto 26 .createHmac("sha256", SIGNING_SECRET) 27 .update(signedPayload) 28 .digest("hex"); 29 30 return crypto.timingSafeEqual( 31 Buffer.from(expected), 32 Buffer.from(signature) 33 ); 34 } 35 36 app.post("/webhook", (req, res) => { 37 if (!verifySignature(req)) { 38 return res.status(401).json({ error: "Invalid signature" }); 39 } 40 41 const { event_id, event_type, payload } = req.body; 42 43 // Deduplicate using event_id (idempotency) 44 // In production, check event_id against a cache or database 45 46 if (event_type === "call.transcription") { 47 const transcript = payload.text; 48 console.log(`[${event_id}] Caller said: ${transcript}`); 49 50 return res.json({ 51 text: `You said: ${transcript}. How can I help you today?`, 52 end_call: false, 53 }); 54 } 55 56 res.json({ status: "ok" }); 57 }); 58 59 app.listen(3000, () => console.log("Webhook handler listening on port 3000"));
Flask BYOM Webhook Handler
A Python Flask server handling BYOM webhook events with HMAC-SHA256 signature verification.
1 import hmac 2 import hashlib 3 import os 4 import time 5 from flask import Flask, request, jsonify, abort 6 7 app = Flask(__name__) 8 SIGNING_SECRET = os.environ["VOICEBIP_SIGNING_SECRET"] 9 10 11 def verify_signature(req): 12 signature = req.headers.get("X-Voicebip-Signature", "") 13 timestamp = req.headers.get("X-Voicebip-Timestamp", "") 14 if not signature or not timestamp: 15 return False 16 17 # Reject replays older than 5 minutes 18 if abs(time.time() - int(timestamp)) > 300: 19 return False 20 21 # Signed payload is "{timestamp}.{body}" 22 signed_payload = f"{timestamp}.".encode() + req.get_data() 23 expected = "sha256=" + hmac.new( 24 SIGNING_SECRET.encode(), signed_payload, hashlib.sha256 25 ).hexdigest() 26 return hmac.compare_digest(expected, signature) 27 28 29 @app.route("/webhook", methods=["POST"]) 30 def webhook(): 31 if not verify_signature(request): 32 abort(401, description="Invalid signature") 33 34 payload = request.json 35 event_type = payload.get("event_type") 36 event_id = payload.get("event_id") 37 38 # Deduplicate using event_id (idempotency) 39 # In production, check event_id against a cache or database 40 41 if event_type == "call.transcription": 42 transcript = payload["payload"]["text"] 43 return jsonify({ 44 "text": f"You said: {transcript}. How can I help?", 45 "end_call": False, 46 }) 47 48 return jsonify({"status": "ok"}) 49 50 51 if __name__ == "__main__": 52 app.run(port=3000)
Go Webhook Handler
A Go net/http handler with constant-time HMAC-SHA256 signature comparison.
1 package main 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha256" 6 "crypto/subtle" 7 "encoding/hex" 8 "encoding/json" 9 "fmt" 10 "io" 11 "log" 12 "math" 13 "net/http" 14 "os" 15 "strconv" 16 "time" 17 ) 18 19 var signingSecret = os.Getenv("VOICEBIP_SIGNING_SECRET") 20 21 type WebhookEvent struct { 22 EventID string `json:"event_id"` 23 EventType string `json:"event_type"` 24 Channel string `json:"channel"` 25 AgentID string `json:"agent_id"` 26 Timestamp string `json:"timestamp"` 27 Payload json.RawMessage `json:"payload"` 28 } 29 30 type TranscriptionPayload struct { 31 CallID string `json:"call_id"` 32 Text string `json:"text"` 33 IsFinal bool `json:"is_final"` 34 Confidence float64 `json:"confidence"` 35 } 36 37 type BYOMResponse struct { 38 Text string `json:"text"` 39 EndCall bool `json:"end_call"` 40 } 41 42 // verifySignature checks HMAC-SHA256 over "{timestamp}.{body}". 43 func verifySignature(timestamp string, body []byte, signature string) bool { 44 // Reject replays older than 5 minutes. 45 ts, err := strconv.ParseInt(timestamp, 10, 64) 46 if err != nil { 47 return false 48 } 49 if math.Abs(float64(time.Now().Unix()-ts)) > 300 { 50 return false 51 } 52 // Signed payload is "{timestamp}.{body}" 53 mac := hmac.New(sha256.New, []byte(signingSecret)) 54 mac.Write([]byte(timestamp + ".")) 55 mac.Write(body) 56 expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) 57 return subtle.ConstantTimeCompare([]byte(expected), []byte(signature)) == 1 58 } 59 60 func webhookHandler(w http.ResponseWriter, r *http.Request) { 61 body, err := io.ReadAll(r.Body) 62 if err != nil { 63 http.Error(w, "failed to read body", http.StatusBadRequest) 64 return 65 } 66 defer r.Body.Close() 67 68 signature := r.Header.Get("X-Voicebip-Signature") 69 timestamp := r.Header.Get("X-Voicebip-Timestamp") 70 if !verifySignature(timestamp, body, signature) { 71 http.Error(w, "invalid signature", http.StatusUnauthorized) 72 return 73 } 74 75 var event WebhookEvent 76 if err := json.Unmarshal(body, &event); err != nil { 77 http.Error(w, "invalid JSON", http.StatusBadRequest) 78 return 79 } 80 81 // Deduplicate using event.EventID (idempotency) 82 // In production, check EventID against a cache or database 83 84 switch event.EventType { 85 case "call.transcription": 86 var tp TranscriptionPayload 87 if err := json.Unmarshal(event.Payload, &tp); err != nil { 88 http.Error(w, "invalid payload", http.StatusBadRequest) 89 return 90 } 91 92 log.Printf("[%s] Caller said: %s", event.EventID, tp.Text) 93 94 resp := BYOMResponse{ 95 Text: fmt.Sprintf("You said: %s. How can I help?", tp.Text), 96 EndCall: false, 97 } 98 w.Header().Set("Content-Type", "application/json") 99 json.NewEncoder(w).Encode(resp) 100 101 default: 102 w.Header().Set("Content-Type", "application/json") 103 w.Write([]byte(`{"status":"ok"}`)) 104 } 105 } 106 107 func main() { 108 http.HandleFunc("/webhook", webhookHandler) 109 log.Println("Webhook handler listening on :3000") 110 log.Fatal(http.ListenAndServe(":3000", nil)) 111 }
Next.js API Route
A Next.js 14 App Router POST handler with HMAC-SHA256 signature verification.
1 // app/api/webhook/route.ts 2 import { NextRequest, NextResponse } from "next/server"; 3 import crypto from "crypto"; 4 5 const SIGNING_SECRET = process.env.VOICEBIP_SIGNING_SECRET!; 6 7 function verifySignature(body: string, timestamp: string, signature: string): boolean { 8 // Reject replays older than 5 minutes 9 if (Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)) > 300) return false; 10 11 // Signed payload is "{timestamp}.{body}" 12 const signedPayload = `${timestamp}.${body}`; 13 const expected = 14 "sha256=" + 15 crypto.createHmac("sha256", SIGNING_SECRET).update(signedPayload).digest("hex"); 16 17 return crypto.timingSafeEqual( 18 Buffer.from(expected), 19 Buffer.from(signature) 20 ); 21 } 22 23 export async function POST(req: NextRequest) { 24 const body = await req.text(); 25 const signature = req.headers.get("x-voicebip-signature") ?? ""; 26 const timestamp = req.headers.get("x-voicebip-timestamp") ?? ""; 27 28 if (!verifySignature(body, timestamp, signature)) { 29 return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); 30 } 31 32 const event = JSON.parse(body); 33 34 // Deduplicate using event.event_id (idempotency) 35 // In production, check event_id against a cache or database 36 37 if (event.event_type === "call.transcription") { 38 const transcript = event.payload.text; 39 console.log(`[${event.event_id}] Caller said: ${transcript}`); 40 41 return NextResponse.json({ 42 text: `You said: ${transcript}. How can I help you today?`, 43 end_call: false, 44 }); 45 } 46 47 return NextResponse.json({ status: "ok" }); 48 }
Voice + OpenAI Integration
An Express handler that receives a call.transcription event, forwards the transcript to the OpenAI Chat Completions API, and returns the response. Includes HMAC signature verification.
1 const express = require("express"); 2 const crypto = require("crypto"); 3 const OpenAI = require("openai"); 4 5 const app = express(); 6 const SIGNING_SECRET = process.env.VOICEBIP_SIGNING_SECRET; 7 const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); 8 9 app.use(express.json({ 10 verify: (req, _res, buf) => { req.rawBody = buf; } 11 })); 12 13 function verifySignature(req) { 14 const signature = req.headers["x-voicebip-signature"]; 15 const timestamp = req.headers["x-voicebip-timestamp"]; 16 if (!signature || !timestamp) return false; 17 18 // Reject replays older than 5 minutes 19 if (Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)) > 300) return false; 20 21 // Signed payload is "{timestamp}.{body}" 22 const signedPayload = Buffer.concat([ 23 Buffer.from(timestamp + "."), 24 req.rawBody, 25 ]); 26 const expected = "sha256=" + crypto 27 .createHmac("sha256", SIGNING_SECRET) 28 .update(signedPayload) 29 .digest("hex"); 30 return crypto.timingSafeEqual( 31 Buffer.from(expected), 32 Buffer.from(signature) 33 ); 34 } 35 36 app.post("/webhook", async (req, res) => { 37 if (!verifySignature(req)) { 38 return res.status(401).json({ error: "Invalid signature" }); 39 } 40 41 const { event_type, payload } = req.body; 42 43 if (event_type === "call.transcription") { 44 // Build messages from conversation_history (last 20 turns included by Voicebip) 45 const messages = [ 46 { 47 role: "system", 48 content: 49 "You are a helpful Nigerian customer support agent for Voicebip. " + 50 "Be concise — the caller is on a voice call. Keep responses under 2 sentences.", 51 }, 52 ]; 53 54 if (payload.conversation_history) { 55 for (const turn of payload.conversation_history) { 56 messages.push({ 57 role: turn.role === "caller" ? "user" : "assistant", 58 content: turn.text, 59 }); 60 } 61 } 62 63 // Add the current transcription 64 messages.push({ role: "user", content: payload.text }); 65 66 const completion = await openai.chat.completions.create({ 67 model: "gpt-4o", 68 messages, 69 max_tokens: 150, 70 temperature: 0.7, 71 }); 72 73 const reply = completion.choices[0].message.content; 74 75 return res.json({ 76 text: reply, 77 end_call: false, 78 }); 79 } 80 81 res.json({ status: "ok" }); 82 }); 83 84 app.listen(3000, () => console.log("OpenAI webhook handler on port 3000"));
Voice + Claude Integration
A Python handler that receives a call.transcription event, forwards the transcript to the Anthropic Messages API, and returns the response. Includes HMAC signature verification.
1 import hmac 2 import hashlib 3 import os 4 import time 5 import anthropic 6 from flask import Flask, request, jsonify, abort 7 8 app = Flask(__name__) 9 SIGNING_SECRET = os.environ["VOICEBIP_SIGNING_SECRET"] 10 client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) 11 12 13 def verify_signature(req): 14 signature = req.headers.get("X-Voicebip-Signature", "") 15 timestamp = req.headers.get("X-Voicebip-Timestamp", "") 16 if not signature or not timestamp: 17 return False 18 19 # Reject replays older than 5 minutes 20 if abs(time.time() - int(timestamp)) > 300: 21 return False 22 23 # Signed payload is "{timestamp}.{body}" 24 signed_payload = f"{timestamp}.".encode() + req.get_data() 25 expected = "sha256=" + hmac.new( 26 SIGNING_SECRET.encode(), signed_payload, hashlib.sha256 27 ).hexdigest() 28 return hmac.compare_digest(expected, signature) 29 30 31 @app.route("/webhook", methods=["POST"]) 32 def webhook(): 33 if not verify_signature(request): 34 abort(401, description="Invalid signature") 35 36 payload = request.json 37 event_type = payload.get("event_type") 38 39 if event_type == "call.transcription": 40 inner = payload["payload"] 41 42 # Build messages from conversation_history (last 20 turns included by Voicebip) 43 messages = [] 44 if inner.get("conversation_history"): 45 for turn in inner["conversation_history"]: 46 messages.append({ 47 "role": "user" if turn["role"] == "caller" else "assistant", 48 "content": turn["text"], 49 }) 50 51 # Add the current transcription 52 messages.append({"role": "user", "content": inner["text"]}) 53 54 response = client.messages.create( 55 model="claude-sonnet-4-20250514", 56 max_tokens=150, 57 system=( 58 "You are a helpful Nigerian customer support agent for Voicebip. " 59 "Be concise — the caller is on a voice call. Keep responses under 2 sentences." 60 ), 61 messages=messages, 62 ) 63 64 reply = response.content[0].text 65 66 return jsonify({ 67 "text": reply, 68 "end_call": False, 69 }) 70 71 return jsonify({"status": "ok"}) 72 73 74 if __name__ == "__main__": 75 app.run(port=3000)
For the lowest latency, use Voicebip’s hosted AI mode instead of BYOM. Hosted mode runs the AI provider inside Voicebip’s infrastructure with sub-500ms turn times. BYOM adds your webhook round-trip to the total latency. See Best Practices for latency optimization tips.