API ReferenceWebhooks & Notifications

Webhooks & Notifications API

v2.0StableLast updated: March 2026

Receive real-time notifications when events occur in ThinkHive. This page covers the complete notification system: webhooks for programmatic integrations, notification channels for team alerting (Slack, email, PagerDuty), and notification rules for per-agent alert configuration.

This page replaces the previous Webhooks API page. All webhook endpoints documented here are the canonical v1 API. The old page is retained for reference but marked as deprecated.

Authentication

All endpoints require either a session cookie or an API key via the Authorization: Bearer header.

Authorization: Bearer thk_your_api_key

See Authentication for details on obtaining and managing API keys.


Webhooks

Webhooks deliver event payloads to your HTTP endpoints in real time. ThinkHive signs every delivery with HMAC-SHA256, retries on failure, and opens a circuit breaker after repeated errors.

List Webhooks

GET /api/v1/explainer/webhooks

Returns all webhooks for the authenticated company.

Response:

{
  "success": true,
  "data": {
    "webhooks": [
      {
        "id": "a1b2c3d4-...",
        "name": "Production Alerts",
        "url": "https://your-app.com/webhooks/thinkhive",
        "events": ["trace.analyzed", "failure.detected"],
        "filters": {
          "agentIds": ["agent_abc123"],
          "minSeverity": "high"
        },
        "isActive": true,
        "lastTriggeredAt": "2026-03-10T08:30:00Z",
        "consecutiveFailures": 0,
        "circuitBreakerUntil": null
      }
    ]
  }
}

Create Webhook

POST /api/v1/explainer/webhooks

Request Body:

FieldTypeRequiredDescription
namestringYesDisplay name (1-255 characters)
urlstringYesHTTPS endpoint URL
eventsstring[]YesOne or more event types
filtersobjectNoOptional delivery filters (see below)

Filters:

FieldTypeDescription
filters.agentIdsstring[]Only deliver events for these agents
filters.minSeveritystringMinimum severity level
filters.outcomesstring[]Filter by trace outcomes

Request example:

{
  "name": "Production Alerts",
  "url": "https://your-app.com/webhooks/thinkhive",
  "events": ["failure.detected", "case.created", "drift.detected"],
  "filters": {
    "agentIds": ["agent_abc123"],
    "minSeverity": "high"
  }
}

Response (201 Created):

{
  "success": true,
  "data": {
    "webhook": {
      "id": "a1b2c3d4-...",
      "name": "Production Alerts",
      "url": "https://your-app.com/webhooks/thinkhive",
      "events": ["failure.detected", "case.created", "drift.detected"],
      "filters": { "agentIds": ["agent_abc123"], "minSeverity": "high" },
      "isActive": true,
      "secret": "d4e5f6a7-...b8c9d0e1-..."
    }
  }
}
⚠️

The secret is returned only on creation. Store it securely — you will need it to verify signatures.

Update Webhook

PATCH /api/v1/explainer/webhooks/:id

All fields are optional. Only provided fields are updated.

Request Body:

FieldTypeDescription
namestringUpdated display name
urlstringUpdated endpoint URL
eventsstring[]Updated event subscriptions
filtersobjectUpdated delivery filters
isActivebooleanEnable or disable the webhook

Response:

{
  "success": true,
  "data": { "message": "Webhook updated" }
}

Delete Webhook

DELETE /api/v1/explainer/webhooks/:id

Permanently removes the webhook and all associated delivery history.

Response:

{
  "success": true,
  "data": { "message": "Webhook deleted" }
}

Test Webhook

POST /api/v1/explainer/webhooks/:id/test

Enqueues a test delivery with event type webhook.test. Use this to verify your endpoint is reachable and correctly validating signatures.

Response:

{
  "success": true,
  "data": {
    "message": "Test webhook queued for delivery",
    "deliveryId": "test_dlv_...",
    "eventId": "test_..."
  }
}

Rotate Secret

POST /api/v1/explainer/webhooks/:id/rotate-secret

Immediately replaces the webhook secret. All subsequent deliveries use the new secret.

Response:

{
  "success": true,
  "data": {
    "message": "Webhook secret rotated",
    "secret": "new-secret-value"
  }
}

Rotate Secret (Graceful)

POST /api/v1/explainer/webhooks/:id/rotate-secret-graceful

Rotates the secret with a grace period during which both old and new secrets are valid. This allows you to update your verification code without downtime.

Request Body:

FieldTypeDefaultDescription
gracePeriodHoursnumber24Hours during which both secrets are valid

Response:

{
  "success": true,
  "data": {
    "message": "Webhook secret rotated with grace period",
    "newSecret": "new-secret-value",
    "gracePeriodUntil": "2026-03-11T08:30:00.000Z",
    "note": "Both old and new secrets will be valid until 2026-03-11T08:30:00.000Z"
  }
}

Reset Circuit Breaker

POST /api/v1/explainer/webhooks/:id/reset-circuit-breaker

Resets the circuit breaker after you have resolved the issue causing delivery failures. This clears the consecutive failure count and removes the cooldown window.

Response:

{
  "success": true,
  "data": { "message": "Circuit breaker reset" }
}

Delivery Metrics

GET /api/v1/explainer/webhooks/:id/metrics

Returns delivery statistics for the last 24 hours, 7 days, and all time, plus the 10 most recent errors.

Response:

{
  "success": true,
  "data": {
    "webhookId": "a1b2c3d4-...",
    "status": {
      "isActive": true,
      "circuitBreakerOpen": false,
      "circuitBreakerUntil": null,
      "consecutiveFailures": 0,
      "lastTriggeredAt": "2026-03-10T08:30:00Z"
    },
    "allTime": {
      "total": 1250,
      "delivered": 1230,
      "failed": 15,
      "deadLetter": 5,
      "pending": 0,
      "successRate": 98.4,
      "avgResponseTimeMs": 145,
      "avgAttempts": 1.1
    },
    "last24h": { "total": 42, "delivered": 42, "failed": 0, "successRate": 100 },
    "last7d": { "total": 310, "delivered": 305, "failed": 5, "successRate": 98.4 },
    "recentErrors": []
  }
}

List Deliveries

GET /api/v1/explainer/webhooks/:id/deliveries

Query Parameters:

ParameterTypeDefaultDescription
statusstringallFilter by status: pending, delivered, failed, dead_letter
limitnumber50Max results (capped at 100)
offsetnumber0Pagination offset

Response:

{
  "success": true,
  "data": {
    "deliveries": [
      {
        "id": "dlv_001",
        "eventId": "evt_001",
        "eventType": "failure.detected",
        "status": "delivered",
        "attempts": 1,
        "lastStatusCode": 200,
        "lastError": null,
        "responseTimeMs": 120,
        "createdAt": "2026-03-10T08:30:00Z",
        "deliveredAt": "2026-03-10T08:30:00Z"
      }
    ],
    "pagination": { "limit": 50, "offset": 0, "total": 1250 }
  }
}

Replay Delivery

POST /api/v1/explainer/webhooks/:id/deliveries/:deliveryId/replay

Re-sends a failed or dead_letter delivery. A new delivery record is created with the original payload.

Response:

{
  "success": true,
  "data": {
    "message": "Delivery queued for replay",
    "originalDeliveryId": "dlv_001",
    "newDeliveryId": "dlv_002",
    "eventId": "evt_001_replay_1710000000000"
  }
}

Available Events

EventDescription
trace.analyzedTrace analysis completed
failure.detectedA failure was detected in a trace
pattern.newNew failure pattern identified
pattern.updatedExisting failure pattern updated
case.createdNew Case created from clustered failures
fix.createdNew fix proposed for a Case
drift.detectedModel or behavior drift detected
threshold.exceededA configured metric threshold was exceeded
run.completedEvaluation run completed successfully
run.failedEvaluation run failed
webhook.testTest event (sent via the test endpoint)

Webhook Payload

Every webhook delivery includes a JSON payload with the following structure:

{
  "event": "failure.detected",
  "timestamp": "2026-03-10T08:30:00Z",
  "eventId": "evt_a1b2c3d4",
  "schemaVersion": "2.0",
  "data": {
    "companyId": "company_xyz",
    "agentId": "agent_abc123",
    "traceId": "tr_xyz789",
    "failureReason": "Hallucination detected",
    "severity": "high"
  }
}

Headers

Each delivery includes these headers:

HeaderDescription
Content-Typeapplication/json
X-ThinkHive-EventEvent type (e.g., failure.detected)
X-ThinkHive-DeliveryUnique delivery ID
X-ThinkHive-SignatureHMAC-SHA256 signature

Signature Verification

Every webhook delivery is signed with your webhook secret using HMAC-SHA256. Always verify signatures before processing payloads.

import crypto from 'crypto';
 
function verifyWebhookSignature(
  rawBody: Buffer,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
 
  // timingSafeEqual throws if buffers differ in length
  const sigBuf = Buffer.from(signature, 'utf8');
  const expBuf = Buffer.from(expected, 'utf8');
  if (sigBuf.length !== expBuf.length) {
    return false;
  }
 
  return crypto.timingSafeEqual(sigBuf, expBuf);
}
 
// Express middleware example
// IMPORTANT: You need raw body access. Add this BEFORE json() parsing:
//   app.use('/webhooks/thinkhive', express.raw({ type: 'application/json' }));
// This gives you req.body as a Buffer. Parse JSON yourself after verification.
app.post('/webhooks/thinkhive', (req, res) => {
  const signature = req.headers['x-thinkhive-signature'] as string;
  const rawBody: Buffer = req.body; // Buffer from express.raw()
  const isValid = verifyWebhookSignature(
    rawBody,
    signature,
    process.env.THINKHIVE_WEBHOOK_SECRET!
  );
 
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
 
  // Process the event
  const parsed = JSON.parse(rawBody.toString());
  const { event, data } = parsed;
  console.log(`Received ${event}:`, data);
 
  res.status(200).json({ received: true });
});

Retry Policy

Failed webhook deliveries are retried automatically with exponential backoff:

AttemptDelayCumulative
1Immediate0
21 minute1 min
35 minutes6 min
430 minutes36 min
52 hours~2.5 hr
624 hours~26.5 hr

After all retries are exhausted, the delivery is moved to dead_letter status. You can replay dead-letter deliveries using the Replay Delivery endpoint.

Circuit Breaker

If a webhook accumulates consecutive failures, ThinkHive opens a circuit breaker to prevent overloading your endpoint:

  • Circuit breaker opens after 5 consecutive failures
  • While open, new deliveries are queued but not sent
  • The circuit breaker automatically closes after a cooldown period
  • Use the Reset Circuit Breaker endpoint to manually re-enable delivery after fixing the issue

Notification Channels

Notification channels configure company-wide delivery targets for alerts: Slack, email, PagerDuty, or generic webhooks. Sensitive configuration (webhook URLs, routing keys) is encrypted at rest.

List Channels

GET /api/v1/explainer/notification-channels

Returns all configured channels for the authenticated company. Sensitive fields are masked in the response.

Response:

{
  "success": true,
  "data": {
    "channels": [
      {
        "id": "ch_001",
        "channel": "slack",
        "isEnabled": true,
        "config": { "webhookUrl": "https://hooks.slack.com/****" },
        "isVerified": true,
        "createdAt": "2026-01-15T10:00:00Z"
      },
      {
        "id": "ch_002",
        "channel": "email",
        "isEnabled": true,
        "config": { "recipients": ["team@example.com"], "fromName": "ThinkHive Alerts" },
        "isVerified": true,
        "createdAt": "2026-01-15T10:00:00Z"
      }
    ]
  }
}

Create or Update Channel

PUT /api/v1/explainer/notification-channels/:channel

Upserts a channel configuration. The :channel parameter must be one of: slack, email, pagerduty, webhook.

Request Body:

FieldTypeRequiredDescription
isEnabledbooleanNoEnable or disable the channel
configobjectNoChannel-specific configuration (see below)

Slack Configuration

FieldTypeRequiredDescription
webhookUrlstringYesSlack incoming webhook URL (must start with https://hooks.slack.com/)
{
  "isEnabled": true,
  "config": {
    "webhookUrl": "https://hooks.slack.com/services/T00/B00/xxxx"
  }
}

Email Configuration

FieldTypeRequiredDescription
recipientsstring[]YesOne or more email addresses
fromNamestringNoDisplay name for the sender (max 100 chars)
{
  "isEnabled": true,
  "config": {
    "recipients": ["ops@example.com", "team-lead@example.com"],
    "fromName": "ThinkHive Alerts"
  }
}

PagerDuty Configuration

FieldTypeRequiredDescription
routingKeystringYesPagerDuty Events API v2 routing key
serviceIdstringNoPagerDuty service identifier
{
  "isEnabled": true,
  "config": {
    "routingKey": "your-pagerduty-routing-key",
    "serviceId": "PSERVICE01"
  }
}

Webhook Configuration

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint URL
secretstringYesSigning secret for HMAC verification
headersobjectNoCustom headers to include in deliveries
{
  "isEnabled": true,
  "config": {
    "url": "https://your-app.com/notifications",
    "secret": "your_signing_secret",
    "headers": { "X-Custom-Header": "value" }
  }
}

Response:

{
  "success": true,
  "data": {
    "channel": {
      "id": "ch_001",
      "channel": "slack",
      "isEnabled": true,
      "config": { "webhookUrl": "https://hooks.slack.com/****" },
      "isVerified": false,
      "createdAt": "2026-03-10T08:30:00Z"
    }
  }
}

Delete Channel

DELETE /api/v1/explainer/notification-channels/:channel

Removes the channel configuration. The :channel parameter must be one of: slack, email, pagerduty, webhook.

Response:

{
  "success": true,
  "data": { "message": "Channel configuration deleted" }
}

Test Channel

POST /api/v1/explainer/notification-channels/:channel/test

Sends a test notification through the configured channel. Rate limited to one test per channel per minute per company.

Response (success):

{
  "success": true,
  "data": {
    "success": true,
    "message": "Test notification delivered"
  }
}

Response (rate limited, 429):

{
  "success": false,
  "data": { "error": "Rate limited. Wait 1 minute between tests." }
}

Notification Rules

Notification rules define per-agent alerting conditions. When a rule’s conditions are met, ThinkHive sends a notification through the specified channel to the configured recipients.

List Rules

GET /api/notification-rules

Query Parameters:

ParameterTypeRequiredDescription
agentIdstringYesAgent to list rules for

Response:

[
  {
    "id": "rule_001",
    "agentId": "agent_abc123",
    "name": "High-severity failures",
    "description": "Alert on high/critical severity failures",
    "conditions": {
      "event": "failure.detected",
      "minSeverity": "high"
    },
    "channel": "slack",
    "recipients": {
      "slackChannel": "#alerts-prod"
    },
    "cooldownMinutes": 60,
    "lastTriggered": "2026-03-10T07:15:00Z",
    "isActive": true,
    "createdAt": "2026-01-15T10:00:00Z",
    "updatedAt": "2026-03-10T07:15:00Z"
  }
]

Create Rule

POST /api/notification-rules

Request Body:

FieldTypeRequiredDescription
agentIdstringYesAgent this rule applies to
namestringYesDisplay name for the rule
descriptionstringNoOptional description
conditionsobjectYesTrigger conditions (JSON)
channelstringYesDelivery channel: email, slack, webhook, or in_app
recipientsobjectYesChannel-specific recipients (JSON)
cooldownMinutesnumberNoMinimum minutes between triggers (default: 60)
isActivebooleanNoWhether the rule is active (default: true)

Request example:

{
  "agentId": "agent_abc123",
  "name": "Drift Alert",
  "description": "Notify when model drift is detected",
  "conditions": {
    "event": "drift.detected",
    "minSeverity": "medium"
  },
  "channel": "email",
  "recipients": {
    "emails": ["ml-team@example.com"]
  },
  "cooldownMinutes": 120
}

Response (201 Created):

{
  "id": "rule_002",
  "agentId": "agent_abc123",
  "name": "Drift Alert",
  "description": "Notify when model drift is detected",
  "conditions": { "event": "drift.detected", "minSeverity": "medium" },
  "channel": "email",
  "recipients": { "emails": ["ml-team@example.com"] },
  "cooldownMinutes": 120,
  "lastTriggered": null,
  "isActive": true,
  "createdAt": "2026-03-10T08:30:00Z",
  "updatedAt": "2026-03-10T08:30:00Z"
}

Update Rule

PATCH /api/notification-rules/:id

Partially updates a notification rule. All fields are optional.

Request example:

{
  "isActive": false,
  "cooldownMinutes": 30
}

Response:

{
  "id": "rule_002",
  "agentId": "agent_abc123",
  "name": "Drift Alert",
  "isActive": false,
  "cooldownMinutes": 30,
  "updatedAt": "2026-03-10T09:00:00Z"
}

Delete Rule

DELETE /api/notification-rules/:id

Permanently deletes the notification rule.

Response: 204 No Content


User Notification Preferences

Individual users can configure their notification preferences to control which channels are active, minimum severity, and quiet hours.

Get Preferences

GET /api/v1/explainer/notification-preferences

Response:

{
  "success": true,
  "data": {
    "preferences": {
      "emailEnabled": true,
      "slackEnabled": true,
      "inAppEnabled": true,
      "webhookEnabled": true,
      "pagerdutyEnabled": true,
      "minSeverity": "info",
      "quietHoursStart": null,
      "quietHoursEnd": null,
      "quietHoursTimezone": "UTC"
    }
  }
}

Update Preferences

PUT /api/v1/explainer/notification-preferences

Request Body:

FieldTypeDescription
emailEnabledbooleanEnable email notifications
slackEnabledbooleanEnable Slack notifications
inAppEnabledbooleanEnable in-app notifications
webhookEnabledbooleanEnable webhook notifications
pagerdutyEnabledbooleanEnable PagerDuty notifications
minSeveritystringMinimum severity: info, warning, error, critical
quietHoursStartstring|nullStart of quiet hours in HH:MM format
quietHoursEndstring|nullEnd of quiet hours in HH:MM format
quietHoursTimezonestringTimezone for quiet hours (e.g., America/New_York)

All fields are optional. Only provided fields are updated.

Request example:

{
  "minSeverity": "warning",
  "quietHoursStart": "22:00",
  "quietHoursEnd": "08:00",
  "quietHoursTimezone": "America/New_York"
}

Error Codes

StatusDescription
400Missing required fields or invalid input
401Missing or invalid authentication
404Webhook, channel, or rule not found
429Rate limited (test endpoints)
500Internal server error

Next Steps