Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vocobase.com/llms.txt

Use this file to discover all available pages before exploring further.

Webhook Setup

Webhooks notify your server when events occur in Vocobase, such as a call completing. Instead of polling the API, you receive a POST request with the full event payload.

What webhooks are for

When a call finishes (or fails), Vocobase sends a session.completed event to each enabled webhook endpoint. The payload includes:
  • Full conversation transcript with timestamps
  • Call duration and credits used
  • Recording download URL (if recording was enabled)
  • Pre-call variables and post-call extraction results
  • Call metadata (phone numbers, provider, direction)

Configure webhook endpoints

Use webhook endpoints to send the same event to one or more destinations, such as prod, staging, or an internal audit receiver. Each endpoint has its own URL, enabled flag, delivery history, and signing secret.
curl -X POST https://api.vocobase.com/api/v2/config/webhooks \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "prod",
    "url": "https://your-server.com/webhooks/vocobase"
  }'
The response contains a secret that you must save:
{
  "success": true,
  "data": {
    "id": "12345678-abcd-1234-abcd-123456789012",
    "label": "prod",
    "url": "https://your-server.com/webhooks/vocobase",
    "enabled": true,
    "last_delivery_at": null,
    "last_delivery_status": null,
    "created_at": "2026-05-25T10:30:00.000Z",
    "updated_at": "2026-05-25T10:30:00.000Z",
    "secret": "whsec_a1b2c3d4e5f6g7h8i9j0...",
    "message": "Save this secret — it will not be shown again."
  }
}
The endpoint secret is only shown once. Store it in your environment variables immediately. If you lose it, rotate the endpoint secret with POST /config/webhooks/{id}/rotate-secret.

Manage endpoints

List configured endpoints:
curl https://api.vocobase.com/api/v2/config/webhooks \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
Update a label, URL, or enabled state:
curl -X PATCH https://api.vocobase.com/api/v2/config/webhooks/12345678-abcd-1234-abcd-123456789012 \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
Rotate a signing secret:
curl -X POST https://api.vocobase.com/api/v2/config/webhooks/12345678-abcd-1234-abcd-123456789012/rotate-secret \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
Delete an endpoint:
curl -X DELETE https://api.vocobase.com/api/v2/config/webhooks/12345678-abcd-1234-abcd-123456789012 \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"

Labels and limits

  • Labels must be lowercase letters, numbers, and hyphens; start with a letter or number; and be 1-31 characters.
  • The default label is reserved for the legacy single-webhook compatibility route.
  • Each partner can configure up to 5 webhook endpoints.
  • Only enabled endpoints receive new events.
  • Each endpoint signs requests with its own secret.
Existing integrations that use PUT /api/v2/config/webhook still work. That route now creates or replaces the reserved default endpoint and returns a webhook_secret. New integrations should use /config/webhooks.

Requirements

Your webhook endpoint must:
  • Accept POST requests
  • Use HTTPS (HTTP URLs are rejected)
  • Return a 2xx status code within 10 seconds
  • Be publicly accessible from the internet

Signature verification

Every webhook request includes an X-Webhook-Signature header for verifying authenticity. The signature is computed as an HMAC-SHA256 hash of timestamp.body using your webhook secret.

Header format

X-Webhook-Signature: sha256=<hex-encoded-hmac>
X-Webhook-Timestamp: <iso-8601-timestamp>

Verification steps

  1. Extract the X-Webhook-Signature and X-Webhook-Timestamp headers
  2. Construct the signed payload: {timestamp}.{raw_request_body}
  3. Compute HMAC-SHA256 using your webhook secret
  4. Compare the computed signature with the one in the header

Node.js example

const crypto = require("crypto");

// IMPORTANT: Use express.raw() or express.text() middleware on your webhook
// route so req.body is the raw string, not a parsed JSON object.
// Example: app.post("/webhooks/vocobase", express.text({ type: "*/*" }), handler)
function verifyWebhookSignature(req, webhookSecret) {
  const signature = req.headers["x-webhook-signature"];
  const timestamp = req.headers["x-webhook-timestamp"];
  // req.body must be the raw request body string, not a parsed object
  const body = typeof req.body === "string" ? req.body : req.rawBody?.toString() || JSON.stringify(req.body);

  // Reject requests older than 5 minutes to prevent replay attacks
  const timestampMs = new Date(timestamp).getTime();
  if (Math.abs(Date.now() - timestampMs) > 300_000) {
    throw new Error("Webhook timestamp too old");
  }

  // Compute expected signature
  const signedPayload = `${timestamp}.${body}`;
  const expectedSignature =
    "sha256=" +
    crypto
      .createHmac("sha256", webhookSecret)
      .update(signedPayload)
      .digest("hex");

  // Timing-safe comparison
  if (
    !crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    )
  ) {
    throw new Error("Invalid webhook signature");
  }

  return true;
}

// Express.js handler
app.post("/webhooks/vocobase", (req, res) => {
  try {
    verifyWebhookSignature(req, process.env.VOCOBASE_WEBHOOK_SECRET);
  } catch (err) {
    return res.status(401).json({ error: err.message });
  }

  const event = req.body;
  console.log("Received event:", event.event, event.session_id);

  // Process the webhook asynchronously
  processWebhookAsync(event);

  // Return 200 immediately
  res.status(200).json({ received: true });
});

Python example

import hmac
import hashlib
import os
from datetime import datetime, timezone
from flask import Flask, request, jsonify

app = Flask(__name__)

def verify_webhook_signature(request, webhook_secret):
    signature = request.headers.get("X-Webhook-Signature")
    timestamp = request.headers.get("X-Webhook-Timestamp")
    body = request.get_data(as_text=True)

    # Reject requests older than 5 minutes
    ts = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
    age = abs((datetime.now(timezone.utc) - ts).total_seconds())
    if age > 300:
        raise ValueError("Webhook timestamp too old")

    # Compute expected signature
    signed_payload = f"{timestamp}.{body}"
    expected = "sha256=" + hmac.new(
        webhook_secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        raise ValueError("Invalid webhook signature")

@app.post("/webhooks/vocobase")
def handle_webhook():
    try:
        verify_webhook_signature(request, os.environ["VOCOBASE_WEBHOOK_SECRET"])
    except ValueError as e:
        return jsonify({"error": str(e)}), 401

    event = request.json
    # Process asynchronously, return immediately
    return jsonify({"received": True}), 200
Always verify signatures in production. Skipping verification exposes your endpoint to forged requests.

Next steps

Webhook Payloads

See the full payload structure for each event type.

Error Handling

Learn about retry behavior and best practices.