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
- 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
POST
request to your URL. - Validation: your server validates the signature using the shared secret.
- Confirmation: if your API responds with an HTTP 200 status, we consider it delivered; otherwise, we will make up to 3 automatic retry attempts.
- Logs: you can track each delivery, status, and response in the Event Logs interface.
- 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
- Open the dropdown menu in the top-right corner and select Webhooks.
- Click Create (top-right corner).
- 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
).
- Click Save. The status will become Active, and you will start receiving notifications immediately.
Payload sent
Each request has:
- Headers
x-timestamp: <ISO 8601 timestamp of the event creation>
x-signature: <HMAC-SHA256 of `${body}:${timestamp}`>
Content-Type: application/json
- Body (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
body
and thetimestamp
from thex-timestamp
header. - Locally calculate the HMAC-SHA256 of
${payload}:${timestamp}
using your secret. - Securely compare it with the
x-signature
header. - (Optional) Reject requests with a timestamp outside a specific window—for example, ±5 minutes.
// 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');
});
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:
- Install ngrok:
# For macOS:
brew install ngrok
For other operating systems, consult the official ngrok documentation.
- Start a tunnel pointing to your local port (e.g., 3000):
ngrok http 3000
- Copy 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 /webhook
requests, and watch the console. - Go to Webhooks → [your webhook] → Event Logs to see the status, attempts, and your server's response.