Skip to content

Webhooks

Webhooks allow external systems to receive real-time notifications whenever important events occur on the SipPulse AI platform. Instead of polling our API, we automatically send an HTTP request to your server as soon as something happens.

Why Use Webhooks?

  • Instant response: Receive event data the moment it occurs
  • Scalability: Your system only processes events when necessary, saving resources
  • Automation: Trigger internal workflows (sending emails, updating databases, alerts) without manual intervention

How the Flow Works

  1. Registration: You create a webhook by providing a destination URL, an HMAC secret, and specifying which events you want to receive.
  2. Trigger: Whenever a registered event occurs, our platform generates a JSON payload and sends a POST request to your URL.
  3. Validation: Your server validates the signature using the shared secret.
  4. Confirmation: If your API responds with HTTP 200, we consider it delivered; otherwise, we make up to 3 automatic retry attempts.
  5. Logs: You can track each delivery, status, and response in the Event Logs interface.
  6. Retry: If delivery fails, the system retries up to 3 times. You can also manually resend from the logs interface.

Creating a Webhook

  1. Navigate to Webhooks in the sidebar menu
  2. Click the Create button
  3. Fill in the configuration:
    • URL: A public endpoint (HTTPS recommended) that will receive the POST requests
    • Secret: A private string you define, used to generate the HMAC signature. Choose a secure value and store it safely — you'll need it to validate request signatures
    • Events: Select the events you want to receive notifications for
  4. Click Save. The status becomes Active and you'll start receiving notifications immediately.

Resource Counters

If your plan has a webhook limit, you'll see a counter (e.g., "2/5 Webhooks") at the top of the page. When the limit is reached, the Create button is disabled.

Available Events

Thread Events

EventDescription
thread.createdA new conversation was started
thread.closedA conversation was closed

Voice Events

EventDescription
voice.start_callA voice call was initiated
voice.end_callA voice call was ended

WhatsApp Events

EventDescription
whatsapp.inboundAn inbound message was received on WhatsApp
whatsapp.outboundAn outbound message was sent via WhatsApp

AI Model Events

EventDescription
llm.completionAn LLM response was generated
llm.streamLLM streaming response in progress
stt.transcriptionA speech-to-text transcription was completed
tts.synthesisA text-to-speech synthesis was completed

Payload Format

Each webhook request includes:

Headers

text
x-timestamp: <ISO 8601 timestamp of the event creation>
x-signature: <HMAC-SHA256 of `${body}:${timestamp}`>
Content-Type: application/json

Body (Example)

json
{
  "id": "c93e2b69-88c8-4c19-b85c-1a57d73f6ea7",
  "event": "thread.closed",
  "timestamp": "2025-04-30T12:34:56.789Z",
  "payload": {
    "id": "thr_01966dd65ad074f69cd4346ddcb7a582",
    "title": "Initial Greeting",
    "history": [ /* ... */ ]
  }
}

How to Validate the Signature

To ensure the request actually came from SipPulse AI and was not altered:

  1. Retrieve the body and the timestamp from the x-timestamp header.
  2. Locally calculate the HMAC-SHA256 of ${payload}:${timestamp} using your secret.
  3. Securely compare it with the x-signature header.
  4. (Optional) Reject requests with a timestamp outside a specific window — for example, ±5 minutes.
typescript
import express, { Request, Response } from 'express';
import { createHmac } from 'crypto';

const app = express();
app.use(express.json());

const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET || '';

function isSignatureValid(payload: string, timestamp: string, signature: string): boolean {
  const message = `${payload}:${timestamp}`;
  const hmac = createHmac("sha256", WEBHOOK_SECRET)
    .update(message)
    .digest("hex");

  return hmac === signature;
}

app.post('/webhook', (req: Request, res: Response) => {
  const payload = JSON.stringify(req.body);
  const timestamp = req.headers['x-timestamp'] as string;
  const signature = req.headers['x-signature'] as string;

  // Check if the timestamp is within a 5-minute window
  if (Math.abs(Date.now() - Date.parse(timestamp)) > 5 * 60 * 1000) {
    return res.status(400).json({ error: 'Timestamp expired' });
  }

  // Validate the signature
  if (!isSignatureValid(payload, timestamp, signature)) {
    return res.status(400).json({ error: 'Invalid signature' });
  }

  // Process the received event
  console.log('Event received:', req.body);
  res.status(200).json({ status: 'ok' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
python
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
import os
from datetime import datetime, timezone

app = FastAPI()

WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')

def is_signature_valid(payload: str, timestamp: str, signature: str) -> bool:
    message = f"{payload}:{timestamp}".encode()
    hmac_obj = hmac.new(WEBHOOK_SECRET.encode(), message, hashlib.sha256)
    expected_signature = hmac_obj.hexdigest()
    return hmac.compare_digest(expected_signature, signature)

@app.post("/webhook")
async def webhook(request: Request):
    body = await request.body()
    payload = body.decode()
    timestamp_str = request.headers.get('x-timestamp')
    signature = request.headers.get('x-signature')

    # Check if the timestamp is within a 5-minute window
    event_time = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
    if abs((datetime.now(timezone.utc) - event_time).total_seconds()) > 300:
        raise HTTPException(status_code=400, detail="Timestamp expired")

    # Validate the signature
    if not is_signature_valid(payload, timestamp_str, signature):
        raise HTTPException(status_code=400, detail="Invalid signature")

    # Process the received event
    print("Event received:", payload)
    return {"status": "ok"}

Importance of Signature Validation

Verifying the HMAC signature is a critical step for the security of your integration:

Risks of Not Validating

  • Spoofing attacks: Anyone who knows your endpoint can send fake payloads simulating events from SipPulse AI
  • Malicious data injection: Without validation, attackers can inject manipulated data to compromise your system
  • Replay attacks: Without checking the timestamp, old requests can be captured and resent repeatedly
  • Accidental process activation: Fake events can trigger unwanted operations in your system

Benefits of Validation

  • Authenticity guarantee: Confirmation that the request genuinely came from SipPulse AI
  • Data integrity: Assurance that the payload was not altered during transmission
  • Timeliness: Verifying the timestamp prevents the reuse of old requests
  • Non-repudiation: Cryptographic proof of the message's origin

DANGER

Never store the webhook secret in public source code or in variables exposed to the front-end.

Event Logs

Each webhook has an Event Logs page where you can monitor delivery status:

  • Status: Whether the delivery succeeded (200) or failed
  • Attempts: Number of delivery attempts made
  • Response: Your server's response body
  • Timestamp: When the event was triggered

Use the pagination controls to navigate through historical logs. You can manually resend any failed delivery from this interface.

Local Testing with ngrok

To develop and test without deploying:

  1. Install ngrok:

    bash
    # For macOS:
    brew install ngrok

    For other operating systems, consult the official ngrok documentation.

  2. Start a tunnel pointing to your local port (e.g., 3000):

    bash
    ngrok http 3000
  3. Copy the generated HTTPS URL (e.g., https://abc123.ngrok.io) and use it as the URL when registering the webhook.

  4. Run your local application, receive the POST /webhook requests, and watch the console.

  5. Go to Webhooks → [your webhook] → Event Logs to see the status, attempts, and your server's response.