Webhooks

Voxy delivers signed JSON events to your URL on every action your workspace cares about — a call ending, a complaint being detected, a lead going hot. There are two channels you can configure:

  • Notifications → Webhook channel (recommended). Manage at /workspace/tools/notifications. Same signing model as below; you also get Email and WhatsApp channels driven by the same event catalog.
  • Legacy /workspace/tools/webhooks. Older per-workspace webhook surface — still supported, but new workspaces should use the Notifications page.

Event catalog

Group your subscriptions by what you actually want notified about. Every event ships the same envelope shape:

{
  "event": "complaint.detected",
  "emittedAt": "2026-05-21T09:14:00.123Z",
  "workspace": { "id": "01HZ…", "name": "Acme Co." },
  "payload": { /* event-specific shape — see below */ }
}

Calls

  • call.ended — every call after the AI summary lands.
  • call.inbound_answered, call.outbound_placed, call.missed, call.transferred_to_human.

Customer signals

  • complaint.detected — payload adds ticket { id, severity, summary, detailUrl }.
  • positive_review.captured, spam.detected.

Operations

  • callback.requested — payload adds callback { id, scheduledFor, reason }.
  • booking.created / booking.confirmed / booking.cancelled — payload adds booking { id, kind, title, scheduledAt, totalMinorUnits, detailUrl }.
  • info_request.created — agent couldn't answer; payload adds infoRequest { id, question, context, slaDueAt, detailUrl }.
  • info_request.responded — operator answered; payload adds response + respondedBy.
  • info_request.unreachable — 3 callback attempts failed.

Leads

  • lead.upgraded_to_hot, lead.downgraded_to_cold, lead.went_dead — payload carries the lead row + the previous score / temperature so you can render a diff.

Campaigns

  • campaign.started, campaign.paused, campaign.completed — payload adds stats (total, attempted, answered, completed).

Billing

  • quota.minutes_low, quota.exhausted — payload carries remaining minutes + a billing URL.

Delivery semantics

  • At-least-once. Every delivery carriesx-voxy-delivery-id (ULID). De-dupe on it with a 24-hour lookback.
  • Timeout. 8 seconds per attempt — your consumer must accept and queue the work, not process it inline.
  • Failure handling. 5 consecutive failures auto- pauses the channel; you'll see status=paused on the Notifications page with the last error message. Resume by editing the channel and saving.

Signing (HMAC-SHA256)

Configure a Signing secret on the Webhook channel. On every delivery Voxy computes:

signature = "sha256=" + hex(hmac_sha256(secret, raw_body))
header   = "x-voxy-signature: " + signature

Verify before you trust the body. Reject anything where the recomputed digest doesn't match what arrived in the header. Use constant-time comparison.

Request headers

HeaderMeaning
x-voxy-eventThe event key (e.g. complaint.detected).
x-voxy-delivery-idULID — unique per delivery attempt; use for idempotency.
x-voxy-signaturesha256=<hex> when a signing secret is set.
content-typeapplication/json.

Receiving in Node (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.VOXY_WEBHOOK_SECRET!;

app.post('/voxy', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body as Buffer;
  const sig = String(req.headers['x-voxy-signature'] ?? '');
  const expected = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(rawBody)
    .digest('hex');

  // constant-time compare
  if (
    sig.length !== expected.length ||
    !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
  ) {
    return res.status(401).end();
  }

  const event = JSON.parse(rawBody.toString());
  console.log('voxy event', event.event, 'id=' + event.payload?.call?.id);
  res.status(204).end();
});

Testing

Use the test-send button on each channel (/workspace/tools/notifications) to fire a synthetic payload through the full pipeline. The payload matches the live schema for the chosen event so your verifier won't need special-case branches.

Voxy — Smarter Calls. Better Business.