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.

1const express = require("express");
2const crypto = require("crypto");
3
4const app = express();
5const SIGNING_SECRET = process.env.VOICEBIP_SIGNING_SECRET;
6
7// Parse raw body for HMAC verification
8app.use(express.json({
9 verify: (req, _res, buf) => { req.rawBody = buf; }
10}));
11
12function 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
36app.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
59app.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.

1import hmac
2import hashlib
3import os
4import time
5from flask import Flask, request, jsonify, abort
6
7app = Flask(__name__)
8SIGNING_SECRET = os.environ["VOICEBIP_SIGNING_SECRET"]
9
10
11def 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"])
30def 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
51if __name__ == "__main__":
52 app.run(port=3000)

Go Webhook Handler

A Go net/http handler with constant-time HMAC-SHA256 signature comparison.

1package main
2
3import (
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
19var signingSecret = os.Getenv("VOICEBIP_SIGNING_SECRET")
20
21type 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
30type 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
37type BYOMResponse struct {
38 Text string `json:"text"`
39 EndCall bool `json:"end_call"`
40}
41
42// verifySignature checks HMAC-SHA256 over "{timestamp}.{body}".
43func 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
60func 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
107func 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
2import { NextRequest, NextResponse } from "next/server";
3import crypto from "crypto";
4
5const SIGNING_SECRET = process.env.VOICEBIP_SIGNING_SECRET!;
6
7function 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
23export 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.

1const express = require("express");
2const crypto = require("crypto");
3const OpenAI = require("openai");
4
5const app = express();
6const SIGNING_SECRET = process.env.VOICEBIP_SIGNING_SECRET;
7const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
8
9app.use(express.json({
10 verify: (req, _res, buf) => { req.rawBody = buf; }
11}));
12
13function 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
36app.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
84app.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.

1import hmac
2import hashlib
3import os
4import time
5import anthropic
6from flask import Flask, request, jsonify, abort
7
8app = Flask(__name__)
9SIGNING_SECRET = os.environ["VOICEBIP_SIGNING_SECRET"]
10client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
11
12
13def 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"])
32def 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
74if __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.