API Reference
Webhooks
Receive real-time notifications for async events
Webhooks
Webhooks allow you to receive real-time notifications when events occur in ProsodyAI.
Overview
Instead of polling the API, configure webhook endpoints to receive HTTP POST requests when:
- Analysis completes (async mode)
- Sessions end with predictions
- Fine-tuning jobs complete
- Alerts trigger (e.g., high escalation risk)
Creating a Webhook
POST /v1/webhooks{
"url": "https://your-app.com/webhooks/prosody",
"events": ["analysis.complete", "session.end", "alert.triggered"],
"secret": "whsec_your_webhook_secret"
}Response:
{
"webhook": {
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/prosody",
"events": ["analysis.complete", "session.end", "alert.triggered"],
"active": true,
"created_at": "2025-02-22T12:00:00Z"
}
}Event Types
| Event | Description |
|---|---|
analysis.complete | Async analysis finished |
session.end | Conversation session ended |
session.prediction | Prediction threshold crossed |
alert.triggered | Custom alert triggered |
fine_tune.started | Fine-tuning job started |
fine_tune.completed | Fine-tuning job completed |
fine_tune.failed | Fine-tuning job failed |
Webhook Payload
All webhooks include a standard envelope:
{
"id": "evt_xyz789",
"type": "analysis.complete",
"created_at": "2025-02-22T14:30:00Z",
"data": {
// Event-specific data
}
}analysis.complete
{
"id": "evt_xyz789",
"type": "analysis.complete",
"created_at": "2025-02-22T14:30:00Z",
"data": {
"request_id": "req_abc123",
"emotion": "frustrated",
"confidence": 0.87,
"valence": -0.6,
"arousal": 0.7,
"dominance": 0.4,
"state": "frustrated",
"metrics": {
"escalation_risk": "high",
"csat_predicted": 2.3
}
}
}session.end
{
"id": "evt_xyz789",
"type": "session.end",
"created_at": "2025-02-22T14:30:00Z",
"data": {
"session_id": "call-12345",
"duration": 342.5,
"utterance_count": 28,
"final_emotion": "satisfied",
"emotion_trajectory": ["neutral", "confused", "frustrated", "satisfied"],
"predictions": {
"escalation_risk": 0.12,
"churn_risk": 0.08,
"resolution_probability": 0.95,
"predicted_csat": 4.2
},
"summary": {
"dominant_emotion": "confused",
"peak_escalation_risk": 0.65,
"sentiment_change": 0.8
}
}
}alert.triggered
{
"id": "evt_xyz789",
"type": "alert.triggered",
"created_at": "2025-02-22T14:30:00Z",
"data": {
"alert_type": "escalation_risk_high",
"session_id": "call-12345",
"threshold": 0.7,
"value": 0.82,
"emotion": "angry",
"recommended_action": "supervisor_intervention"
}
}Verifying Webhooks
Always verify webhook signatures to ensure requests are from ProsodyAI.
ProsodyAI signs all webhook requests using HMAC-SHA256. The signature is in the X-Prosody-Signature header.
Verification Example
import crypto from 'crypto';
function verifyWebhook(
payload: string,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
// In your webhook handler
app.post('/webhooks/prosody', (req, res) => {
const signature = req.headers['x-prosody-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const event = req.body;
console.log(`Received ${event.type} event`);
res.status(200).send('OK');
});import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, f'sha256={expected}')
# In your webhook handler
@app.post('/webhooks/prosody')
async def handle_webhook(request: Request):
signature = request.headers.get('X-Prosody-Signature')
payload = await request.body()
if not verify_webhook(payload, signature, os.environ['WEBHOOK_SECRET']):
raise HTTPException(status_code=401, detail='Invalid signature')
event = await request.json()
print(f"Received {event['type']} event")
return {'status': 'ok'}package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
)
func verifyWebhook(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expected))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Prosody-Signature")
payload, _ := io.ReadAll(r.Body)
if !verifyWebhook(payload, signature, os.Getenv("WEBHOOK_SECRET")) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
fmt.Println("Webhook verified")
w.WriteHeader(http.StatusOK)
}Managing Webhooks
List Webhooks
curl https://api.prosody.ai/v1/webhooks \
-H "Authorization: Bearer psk_your_api_key"Update Webhook
curl -X PATCH https://api.prosody.ai/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer psk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"events": ["analysis.complete", "session.end"],
"active": true
}'Delete Webhook
curl -X DELETE https://api.prosody.ai/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer psk_your_api_key"Retry Policy
Failed webhook deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the webhook is marked as failing. You'll receive an email notification, and can view failed deliveries in the dashboard.
Best Practices
- Respond quickly - Return 2xx within 5 seconds; process async
- Handle duplicates - Use event
idfor idempotency - Verify signatures - Always validate webhook authenticity
- Use HTTPS - Webhook URLs must be HTTPS in production
- Monitor failures - Set up alerts for webhook delivery issues