The Alerts API lets you monitor your voice agents in real time. Configure alert rules to watch key operational metrics, then receive webhook notifications when something goes wrong.
Key features
- Alert rules - Monitor metrics like turn latency, API errors, function errors, call crashes, and call volume
- Webhook notifications - Receive signed HTTP POST requests when alerts trigger
- Project scoping - Scope alerts to specific projects or monitor account-wide
Available metrics
All count-based metrics represent absolute counts within the evaluation window, not rates. For example, if you set window_duration_seconds: 300 and threshold_value: 10 for api_errors, the alert triggers when there are 10 or more API errors in the 5-minute window.
| Metric | Description | Unit | Typical threshold guidance |
|---|
turn_latency_p50 | Median turn latency | milliseconds | 800-1500ms depending on use case |
turn_latency_p95 | 95th percentile turn latency | milliseconds | 1500-3000ms depending on use case |
api_errors | Number of API errors in window | count | Consider 0 for critical flows |
function_errors | Number of function execution errors in window | count | Consider 0 for critical functions |
call_crashes | Number of call crashes in window | count | Typically 0 (any crash is concerning) |
call_volume | Number of calls in window | count | Depends on expected traffic patterns |
When setting thresholds, consider the metric type and business impact:
- Error metrics (
api_errors, function_errors, call_crashes): Consider setting threshold to 0 for critical flows where any error requires attention
- Latency metrics: Use longer windows (5-15 minutes) to smooth out transient spikes
- Volume metrics: Base thresholds on historical traffic patterns and expected business hours
Alert states
| State | Description | When it occurs |
|---|
ok | The metric is within the configured threshold | After successful evaluation shows metric below threshold |
alert | The metric has breached the threshold and the alert is firing | Metric exceeds threshold during evaluation |
no_data | No data is available for evaluation | No metric data reported during the evaluation window (e.g., no calls) |
unknown | The state could not be determined | Initial state before first evaluation, or evaluation system error |
no_data vs unknown: Use no_data to detect when your agents aren’t receiving traffic (which may itself be a problem). unknown is typically only seen briefly when a rule is first created or if there’s an evaluation system issue.
Quick start
1. Create an alert rule
curl -X POST https://api.poly.ai/v1/alert-rules \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "High turn latency (p95)",
"project_id": "proj_abc123",
"metric": "turn_latency_p95",
"operator": ">=",
"threshold_value": 1500,
"window_duration_seconds": 300
}'
2. Register a webhook endpoint
curl -X POST https://api.poly.ai/v1/webhook-endpoints \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production alert notifications",
"url": "https://example.com/webhooks/polyai",
"event_types": ["alerts.triggered"]
}'
The signing_secret is returned only once when you create a webhook endpoint. Store it securely immediately.Lost your signing secret? You must delete the webhook endpoint and create a new one. There is no way to retrieve or rotate the secret after creation.
3. Check active alerts
curl https://api.poly.ai/v1/alerts \
-H "x-api-key: YOUR_API_KEY"
Webhook delivery
Event types
| Event | Description |
|---|
alerts.triggered | Sent when an alert rule transitions into a firing state |
No alerts.resolved event: Webhooks only fire when an alert transitions into the firing state. There is no automatic notification when alerts recover. To detect recovery, poll GET /v1/alerts or GET /v1/alert-rules to check when the current_state changes back to ok.If you’re building a PagerDuty-style integration, you’ll need to implement polling to auto-resolve incidents.
Webhook payload
{
"event_type": "alerts.triggered",
"alert": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "High turn latency (p95)",
"project_id": "proj_abc123",
"metric": "turn_latency_p95",
"operator": ">=",
"threshold_value": 1500,
"current_value": 1823,
"previous_state": "ok",
"triggered_at": "2024-03-15T10:30:00Z"
}
}
Each webhook request includes these headers:
| Header | Description |
|---|
X-PolyAI-Timestamp | Unix timestamp (seconds) when the webhook was sent |
X-PolyAI-Signature | HMAC-SHA256 signature for verification |
X-PolyAI-Event-ID | Unique event identifier for deduplication |
Retry policy
Failed webhook deliveries are retried with exponential backoff:
| Attempt | Delay | Cumulative time |
|---|
| 1 | Immediate | 0 |
| 2 | 1 minute | 1 minute |
| 3 | 5 minutes | 6 minutes |
| 4 | 15 minutes | 21 minutes |
| 5 | 1 hour | ~1.5 hours |
| 6 | 4 hours | ~5.5 hours |
Retried failures:
- Timeout
- Network error
- HTTP 408, 429, 5xx
Not retried:
Signature verification
Verify webhook signatures to ensure requests are from PolyAI.
Algorithm: HMAC-SHA256
Signed message format: {timestamp}.{raw_request_body}
import hmac
import hashlib
import time
def verify_webhook(payload: bytes, timestamp: str, signature: str, secret: str) -> bool:
# Reject requests older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
# Compute expected signature
message = f"{timestamp}.{payload.decode('utf-8')}"
expected = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(expected, signature)
const crypto = require('crypto');
function verifyWebhook(payload, timestamp, signature, secret) {
// Reject requests older than 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}
// Compute expected signature
const message = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"math"
"strconv"
"time"
)
func verifyWebhook(payload []byte, timestamp, signature, secret string) bool {
// Reject requests older than 5 minutes
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return false
}
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return false
}
// Compute expected signature
message := timestamp + "." + string(payload)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(message))
expected := hex.EncodeToString(mac.Sum(nil))
// Constant-time comparison
return hmac.Equal([]byte(expected), []byte(signature))
}
Use X-PolyAI-Event-ID for deduplication since retries can deliver the same event more than once.
Authentication
All Alerts API endpoints use API key authentication via the x-api-key header. Resources are automatically scoped to your account.
curl https://api.poly.ai/v1/alerts \
-H "x-api-key: YOUR_API_KEY"
Error responses
| Status | Description |
|---|
| 400 | Validation error - check request body or parameters |
| 401 | Missing or invalid API key |
| 404 | Resource not found |
| 500 | Internal server error |
Example error response
{
"message": "Validation failed",
"errors": [
{
"field": "threshold_value",
"message": "must be a non-negative integer"
},
{
"field": "metric",
"message": "must be one of: turn_latency_p50, turn_latency_p95, api_errors, function_errors, call_crashes, call_volume"
}
]
}
List endpoints currently return all results without pagination. For accounts with many alert rules or webhook endpoints, consider filtering by project_id, metric, or enabled status to reduce response size.
Important: PATCH requests use a JSON:API-style envelope, which differs from the flat JSON used in POST/create requests.Creating a resource (POST) uses flat JSON:{
"name": "My alert rule",
"metric": "api_errors",
"threshold_value": 10
}
Updating a resource (PATCH) requires a JSON:API envelope:{
"data": {
"type": "alert-rules",
"id": "00aa00aa-bb11-cc22-dd33-eeeeeeeeeeee",
"attributes": {
"threshold_value": 20
}
}
}
The id in the request body must match the rule_id or endpoint_id in the URL path.
Window duration
The window_duration_seconds field accepts values from 60 seconds (1 minute) to 86400 seconds (24 hours).
Recommended ranges:
- Latency alerts: 300-900 seconds (5-15 minutes) to smooth transient spikes
- Error count alerts: 60-300 seconds (1-5 minutes) for faster detection
- Volume alerts: 300-3600 seconds (5-60 minutes) depending on traffic patterns
Very short windows (under 60 seconds) may produce noisy alerts due to metric collection intervals. Very long windows (over 1 hour) may delay alert detection significantly.