Skip to content
Developers

Outbound webhooks

SixVox POSTs JSON when something happens on a line. Each request is signed so you can reject anything that did not come from your workspace.

Events

  • call.missed — inbound call ended with no answer, busy, or a failed dial
  • call.completed — a call reached a terminal status
  • voicemail.transcribed — a voicemail recording was transcribed
  • lead.captured — a website enquiry was saved
  • booking.created — a calendar booking was created
  • message.received — an inbound SMS or WhatsApp message arrived

Request

The body is JSON: id, type, created_at, workspace_id, and data. Two headers travel with it:

  • X-SixVox-Timestamp — unix seconds
  • X-SixVox-Signature — v1= plus hex HMAC-SHA256 of timestamp + "." + rawBody

Reject timestamps more than 300 seconds from your clock. Compare the signature against the raw body bytes, before you parse JSON, so key order stays intact. Respond with any HTTP status from 200 to 299. Other 4xx responses are not retried. 408, 429, 5xx, and network errors retry after 60 seconds, 5 minutes, 30 minutes, 2 hours, and 6 hours, then the delivery is marked dead.

Node

import { createHmac, timingSafeEqual } from "node:crypto";

const WINDOW_SECONDS = 300;

export function verifySixVoxWebhook(secret, rawBody, timestamp, signature) {
  const stamped = Number(timestamp);
  const now = Math.floor(Date.now() / 1000);
  if (!Number.isFinite(stamped) || Math.abs(now - stamped) > WINDOW_SECONDS) {
    return false;
  }
  const expected =
    "v1=" + createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  if (a.length !== b.length) return false;
  return timingSafeEqual(a, b);
}

// Express: app.use(express.raw({ type: "application/json" }))
// const rawBody = req.body.toString("utf8");
// const ok = verifySixVoxWebhook(
//   process.env.SIXVOX_WEBHOOK_SECRET,
//   rawBody,
//   req.get("X-SixVox-Timestamp"),
//   req.get("X-SixVox-Signature"),
// );

Python

import hashlib
import hmac
import time

WINDOW_SECONDS = 300

def verify_sixvox_webhook(secret: str, raw_body: str, timestamp: str, signature: str) -> bool:
    try:
        stamped = int(timestamp)
    except ValueError:
        return False
    if abs(int(time.time()) - stamped) > WINDOW_SECONDS:
        return False
    digest = hmac.new(
        secret.encode(),
        f"{timestamp}.{raw_body}".encode(),
        hashlib.sha256,
    ).hexdigest()
    expected = f"v1={digest}"
    return hmac.compare_digest(expected, signature)