Skip to content

Webhooks: call_completed events & signatures

Babelbeez can send a webhook at the end of each call so you can push outcomes into tools like n8n, Zapier, Make, or your own backend.

Each webhook delivers:

  • A normalized call status (how the call ended)
  • A human‑readable summary of the conversation
  • A data object with optional structured fields (name, email, appointment_time, etc.)

This page is for developers, agencies, and power‑users who want to:

  • Verify webhook signatures
  • Inspect payload shape
  • Wire Babelbeez into automation tools or custom code

Webhook configuration lives in the Integrations → Webhooks tab for each voice agent. The high‑level “Configure Webhooks” help entry simply links here for the technical details.


1. When webhooks fire

Babelbeez currently sends two event types:

  • call_completed – sent at the end of a real call.
  • webhook_test – sent when you click “Send test webhook” from the Webhooks tab in the dashboard.

You control which voice agents send webhooks by configuring per‑agent settings in the dashboard. A webhook is sent when:

  1. Your account/profile tier has the webhooks feature, and
  2. The chatbot’s Webhook section has:
    • a non‑empty URL
    • a secret
    • Webhook Enabled toggled on

Monitored entities are optional. They only affect the data object (see below). Webhooks will still fire even when you have no monitored entities.


2. HTTP request structure

Babelbeez sends a standard POST request with:

  • Method: POST
  • Body: JSON payload (see examples below)
  • Headers: includes X-Babelbeez-* headers for event name, timestamp, and signature

Platforms like n8n, Zapier, or Make will typically show you a wrapped view of the request (for example, an object with headers and body fields). The contract that matters is:

  1. The JSON body that Babelbeez sends.
  2. The HTTP headers (especially X-Babelbeez-*).

2.1 Example body: call_completed

This is a representative example of what the JSON body looks like for a real call_completed webhook (field names and types are accurate; values are illustrative):

jsonc
{
  "event": "call_completed",
  "session_id": "6c518bb4-eca9-4dd3-a466-1862e597a11d",
  "public_chatbot_id": "ba52827d-3ff0-4f3e-bab6-b11d83a4ac87",
  "user_id": "e6d0bc49-411d-4807-9740-312d6e136263",
  "timestamp": "2026-01-14T10:57:09.176173+00:00",
  "duration_seconds": 75,
  "status": "ended_ai_tool",         // normalized end status
  "summary": "...human readable summary of the call...",
  "data": {
    "name": "Jane Doe"                 // optional structured fields
  }
}

Notes:

  • status is a normalized value derived from how the client ended the session. Examples include:
    • ended_user – user hung up
    • ended_timeout – inactivity timeout
    • ended_ai_tool – the AI used its end‑conversation tool
    • ended_handoff_whatsapp – user chose WhatsApp handoff
    • aborted_mic_denied – microphone access was denied
  • summary is a short natural‑language recap of the call.
  • data is derived from the backend’s entity extraction pipeline:
    • If you configured monitored entities for this chatbot and the model successfully extracted them, they are returned here as { [entityKey]: string | null }.
    • If no entities are configured or nothing was reliably captured, data will be an empty object: {}.

2.2 Example body: webhook_test

When you click “Send test webhook” in the dashboard, Babelbeez sends a synthetic webhook_test payload. It uses the same signing logic and headers but with placeholder data:

jsonc
{
  "event": "webhook_test",
  "session_id": "8a7d0f92-fc7d-4e9a-b049-b4d62d0c0b8f",
  "public_chatbot_id": "ba52827d-3ff0-4f3e-bab6-b11d83a4ac87",
  "user_id": "e6d0bc49-411d-4807-9740-312d6e136263",
  "timestamp": "2026-01-14T11:02:00.000000+00:00",
  "duration_seconds": 0,
  "status": "test",
  "summary": "This is a test webhook from Babelbeez.",
  "data": {
    "email": "test_value",
    "appointment_time": "test_value"
  }
}

Use webhook_test events to:

  • Teach tools like n8n, Zapier, or Make about the payload shape.
  • Verify that your endpoint URL, secret, and headers are wired correctly.

3. Headers & signature scheme

Each webhook includes the following key headers:

  • Content-Type: application/json
  • X-Babelbeez-Event: call_completed | webhook_test
  • X-Babelbeez-Timestamp: <ISO8601 timestamp>
  • X-Babelbeez-Signature: v1=<hex>
  • User-Agent: Babelbeez-Webhook/1.0

3.1 How the signature is computed

  1. Serialize the JSON payload to a string (this becomes the raw HTTP body).

  2. Compute the signing base string:

    text
    <timestamp>.<json_payload>

    where:

    • <timestamp> is the value from the X-Babelbeez-Timestamp header.
    • <json_payload> is the exact request body as sent (raw bytes), not a re‑serialized object.
  3. Compute the HMAC using your per‑chatbot webhook secret:

    • Algorithm: HMAC-SHA256
    • Key: the Webhook secret you configured in the dashboard.
    • Message: the signing base string above, as bytes.
  4. Hex‑encode the resulting digest.

  5. The final header value is:

    text
    X-Babelbeez-Signature: v1=<hex_digest>

On your side, you should repeat this process and compare your computed signature to the value from X-Babelbeez-Signature.

Babelbeez includes a timestamp in X-Babelbeez-Timestamp but does not enforce any clock skew policy on your behalf.

On your receiver, you can add an extra check such as:

  • Parse X-Babelbeez-Timestamp as UTC.
  • Reject requests where the timestamp is more than, e.g., 5–10 minutes away from your current time.

This helps reduce the risk of a signed request being replayed later.


4. Verifying signatures in Node.js (Express example)

The crucial part is to get access to the raw request body bytes. Many frameworks will automatically parse JSON and discard the original bytes, which breaks signature verification if you re‑serialize.

Below is an example using Express:

ts
import express from 'express';
import crypto from 'crypto';

const app = express();

// 1) Use express.raw() so we keep the raw body for HMAC
app.post(
  '/webhooks/babelbeez',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const webhookSecret = process.env.BABELBEEZ_WEBHOOK_SECRET!;

    const signatureHeader = req.header('x-babelbeez-signature') || '';
    const timestamp = req.header('x-babelbeez-timestamp') || '';

    // Expect format: v1=<hex>
    const expectedPrefix = 'v1=';
    if (!signatureHeader.startsWith(expectedPrefix)) {
      return res.status(400).send('Invalid signature format');
    }

    const receivedSignature = signatureHeader.slice(expectedPrefix.length);

    const rawBody = req.body as Buffer;
    const signingBase = `${timestamp}.${rawBody.toString('utf8')}`;

    const hmac = crypto
      .createHmac('sha256', webhookSecret)
      .update(signingBase, 'utf8')
      .digest('hex');

    const computedSignature = hmac;

    // Constant‑time comparison to avoid timing attacks
    const valid = crypto.timingSafeEqual(
      Buffer.from(computedSignature, 'hex'),
      Buffer.from(receivedSignature, 'hex')
    );

    if (!valid) {
      return res.status(401).send('Signature mismatch');
    }

    // At this point, the request is authentic; now parse the JSON
    let payload: any;
    try {
      payload = JSON.parse(rawBody.toString('utf8'));
    } catch (err) {
      return res.status(400).send('Invalid JSON');
    }

    // Example: route by event type
    const event = payload.event;

    if (event === 'call_completed') {
      // Handle real call outcomes
      // e.g. upsert into your DB, push into a queue, etc.
    } else if (event === 'webhook_test') {
      // Handle test events (optional)
    }

    res.status(200).send('ok');
  }
);

app.listen(3000, () => {
  console.log('Listening for Babelbeez webhooks on port 3000');
});

Key points:

  • Use express.raw() for the route so you get the raw body as a Buffer.
  • Build the signing base string from the header timestamp + raw body.
  • Use your chatbot’s webhook secret as the HMAC key.
  • Only parse JSON after the signature has been verified.

5. Verifying signatures in Python (FastAPI example)

Here is a similar example using FastAPI:

py
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
import os

app = FastAPI()


def timing_safe_compare(a: str, b: str) -> bool:
  return hmac.compare_digest(a, b)


@app.post('/webhooks/babelbeez')
async def handle_babelbeez_webhook(request: Request):
  webhook_secret = os.environ['BABELBEEZ_WEBHOOK_SECRET']

  signature_header = request.headers.get('x-babelbeez-signature', '')
  timestamp = request.headers.get('x-babelbeez-timestamp', '')

  prefix = 'v1='
  if not signature_header.startswith(prefix):
    raise HTTPException(status_code=400, detail='Invalid signature format')

  received_sig = signature_header[len(prefix):]

  raw_body = await request.body()
  signing_base = f"{timestamp}.{raw_body.decode('utf-8')}"

  computed_sig = hmac.new(
    key=webhook_secret.encode('utf-8'),
    msg=signing_base.encode('utf-8'),
    digestmod=hashlib.sha256,
  ).hexdigest()

  if not timing_safe_compare(computed_sig, received_sig):
    raise HTTPException(status_code=401, detail='Signature mismatch')

  # Signature ok – now parse JSON
  try:
    payload = await request.json()
  except Exception:
    raise HTTPException(status_code=400, detail='Invalid JSON body')

  event = payload.get('event')

  if event == 'call_completed':
    # Handle real call outcomes
    ...
  elif event == 'webhook_test':
    # Handle test events (optional)
    ...

  return {"status": "ok"}

Key points:

  • Use await request.body() to get raw bytes for HMAC calculation.
  • Build the signing base string exactly as described.
  • Only parse JSON after verification succeeds.

6. Working with the payload

Once you’ve verified the signature and parsed the JSON body, a typical handler looks like this:

  1. Check payload.event:
    • call_completed – real call outcome.
    • webhook_test – test payload triggered from the dashboard.
  2. Read high‑level metadata:
    • session_id
    • public_chatbot_id
    • user_id
    • timestamp
    • duration_seconds
    • status
    • summary
  3. Consume data:
    • data is a flat object:

      jsonc
      {
        "email": "[email protected]",
        "name": "Jane Doe",
        "appointment_time": "2026-01-20T15:30:00Z"
      }
    • Keys come from the entity keys you configured on the chatbot (e.g. email, name, appointment_time).

    • Values are strings or null if the model could not reliably extract a value.

Common patterns:

  • Insert each call_completed into your own calls / conversations table.
  • Use data fields to:
    • Create or update contacts in your CRM.
    • Create a ticket or task in your helpdesk.
    • Schedule follow‑ups based on captured appointment times.

7. Using Babelbeez webhooks with n8n, Zapier, or Make

No‑code/low‑code tools usually handle HTTP parsing for you. A typical setup is:

  1. Create a Webhook trigger in your tool (n8n, Zapier, Make, etc.).
  2. Copy the generated URL into your agent’s Webhook URL field in Babelbeez and set your Webhook secret.
  3. Click “Send test webhook” in the Babelbeez dashboard.
  4. Back in your tool, inspect the received payload:
    • Confirm the X-Babelbeez-* headers are present.
    • Look at body.data to see your monitored entity keys.
  5. Map fields in body.data into actions:
    • e.g. create a contact using data.email and data.name.
    • e.g. create a calendar event or task using data.appointment_time.

Many of these tools do not require you to implement signature verification manually, but you can often add a custom code step or HTTP middleware if you want to enforce HMAC verification there as well.


8. Security recommendations

  • Always use HTTPS for your webhook endpoints.
  • Keep your webhook secret private and rotate it periodically; update the secret in the Babelbeez dashboard when you do so.
  • Treat webhook payloads as sensitive data (they may include personal information such as names, email addresses, or appointment details).
  • Implement signature verification for any production or security‑sensitive workflows.

With these patterns in place, you can safely use Babelbeez webhooks as the bridge between real customer conversations and your automation stack.

© 2025 Babelbeez. All rights reserved.