Use webhooks when your systems need to react to PolyAI events in real time — for example, triggering an incident response when an alert fires, or updating a dashboard when an alert resolves. If you only need to check alert status on demand, use the Alerts API directly instead.
The Webhooks API lets you register HTTP endpoints that receive real-time notifications when events occur in your PolyAI account. Webhooks are currently used by the Alerts API and will expand to other services in the future.
Key features
- Signed delivery - Every webhook includes an HMAC-SHA256 signature you can verify
- Automatic retries - Failed deliveries retry with exponential backoff
- Secret rotation - Rotate signing secrets without recreating the endpoint
Limits
| Resource | Maximum per account |
|---|
| Webhook endpoints | 10 |
Requests to create a webhook endpoint beyond the limit return a 409 Conflict error.
Event types
| Event | Description |
|---|
alerts.triggered | An alert rule transitioned into a firing state |
alerts.resolved | A firing alert transitioned back to ok |
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 Webhooks API endpoints use API key authentication via the x-api-key header. Resources are automatically scoped to your account.
API keys are not yet available through self-service. To request access, email developers@poly.ai.