Skip to content

Webhooks

Webhooks allow external systems to receive real-time notifications whenever important events occur on the SipPulse AI platform. Instead of you having to poll 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 an HTTP 200 status, we consider it delivered; otherwise, we will 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 the delivery fails, the system will try to resend the webhook up to 3 times. You can also manually resend it from the logs interface.

Create a webhook

  1. Open the dropdown menu in the top-right corner and select Webhooks.
  2. Click Create (top-right corner).
  3. Fill in the fields:
  • URL: a public endpoint (HTTPS recommended) that will receive the POST requests.
  • Secret: a private string you define, used to generate the HMAC signature. Make sure to choose a secure value and store it in a safe place, as it will be needed to validate the request signatures.
  • Events: check the desired events (e.g., thread.created, thread.closed, thread.removed).
  1. Click Save. The status will become Active, and you will start receiving notifications immediately.

Payload sent

Each request has:

  • 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 necessary libraries
import express, { Request, Response } from 'express';
import { createHmac } from 'crypto';

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

// Shared secret used for validation
const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET || '';

/**
 * Function to validate the webhook signature.
 * @param payload - The request body as a string.
 * @param timestamp - The timestamp sent in the header.
 * @param signature - The signature sent in the header.
 * @returns true if the signature is valid, false otherwise.
 */
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
import time
from datetime import datetime, timezone

app = FastAPI()

# Shared secret used for validation
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')

def is_signature_valid(payload: str, timestamp: str, signature: str) -> bool:
   """
   Validates the webhook signature.
   :param payload: The request body as a string.
   :param timestamp: The timestamp sent in the header.
   :param signature: The signature sent in the header.
   :return: True if the signature is valid, False otherwise.
   """
   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 webhooks

  • 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.

As an asynchronous communication method exposed to the internet, unvalidated webhooks represent a significant attack vector. Using HMAC-SHA256 with a shared secret is a robust and widely adopted method to protect this communication channel.

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

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.

  1. Start a tunnel pointing to your local port (e.g., 3000):
bash
ngrok http 3000
  1. Copy the generated HTTPS URL (e.g., https://abc123.ngrok.io) and use it as the URL when registering the webhook.
  2. Run your local application, receive the POST /webhook requests, and watch the console.
  3. Go to Webhooks → [your webhook] → Event Logs to see the status, attempts, and your server's response.