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
- Registration: You create a webhook by providing a destination URL, an HMAC secret, and specifying which events you want to receive.
- Trigger: Whenever a registered event occurs, our platform generates a JSON payload and sends a
POSTrequest to your URL. - Validation: Your server validates the signature using the shared secret.
- Confirmation: If your API responds with HTTP 200, we consider it delivered; otherwise, we make up to 3 automatic retry attempts.
- Logs: You can track each delivery, status, and response in the Event Logs interface.
- Retry: If delivery fails, the system retries up to 3 times. You can also manually resend from the logs interface.
Creating a Webhook
- Navigate to Webhooks in the sidebar menu
- Click the Create button
- Fill in the configuration:
- URL: A public endpoint (HTTPS recommended) that will receive the
POSTrequests - 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
- URL: A public endpoint (HTTPS recommended) that will receive the
- 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
| Event | Description |
|---|---|
thread.created | A new conversation was started |
thread.closed | A conversation was closed |
Voice Events
| Event | Description |
|---|---|
voice.start_call | A voice call was initiated |
voice.end_call | A voice call was ended |
WhatsApp Events
| Event | Description |
|---|---|
whatsapp.inbound | An inbound message was received on WhatsApp |
whatsapp.outbound | An outbound message was sent via WhatsApp |
AI Model Events
| Event | Description |
|---|---|
llm.completion | An LLM response was generated |
llm.stream | LLM streaming response in progress |
stt.transcription | A speech-to-text transcription was completed |
tts.synthesis | A text-to-speech synthesis was completed |
Payload Format
Each webhook request includes:
Headers
x-timestamp: <ISO 8601 timestamp of the event creation>
x-signature: <HMAC-SHA256 of `${body}:${timestamp}`>
Content-Type: application/jsonBody (Example)
{
"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:
- Retrieve the
bodyand thetimestampfrom thex-timestampheader. - Locally calculate the HMAC-SHA256 of
${payload}:${timestamp}using your secret. - Securely compare it with the
x-signatureheader. - (Optional) Reject requests with a timestamp outside a specific window — for example, ±5 minutes.
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');
});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:
Install ngrok:
bash# For macOS: brew install ngrokFor other operating systems, consult the official ngrok documentation.
Start a tunnel pointing to your local port (e.g., 3000):
bashngrok http 3000Copy the generated HTTPS URL (e.g.,
https://abc123.ngrok.io) and use it as the URL when registering the webhook.Run your local application, receive the
POST /webhookrequests, and watch the console.Go to Webhooks → [your webhook] → Event Logs to see the status, attempts, and your server's response.
