Webhooks
Receive real-time notifications when risk thresholds are exceeded.
Overview
Webhooks allow you to receive HTTP POST requests when evaluations exceed configured thresholds. This enables real-time alerting, logging, and escalation workflows without polling.
Event Types
| Event | Source | Description |
|---|---|---|
evaluate.alert | /v1/evaluate | User risk severity meets or exceeds your configured threshold |
oversight.alert | /v1/oversight/* | AI behavior concern level is high or critical |
oversight.ingestion.complete | /v1/oversight/ingest | Batch ingestion processing has completed |
test.ping | Dashboard/API | Test event to verify your endpoint |
Setting Up Webhooks
Configure webhooks in the dashboard or via the API. You'll need:
- Endpoint URL — HTTPS URL to receive POST requests (localhost allowed for testing)
- Severity threshold — minimum severity to trigger:
none,low,medium,high, orcritical - Include conversation: product-specific context described below
Your Signing Secret
When you create a webhook, NOPE generates a unique signing secret for that endpoint. This secret is used to verify that webhook requests genuinely came from NOPE.
Important: Save Your Secret
- The secret is only shown once when you create the webhook
- Store it securely in your environment variables (e.g.,
NOPE_WEBHOOK_SECRET) - If you lose it, use "Regenerate Secret" in the dashboard — but update your endpoint immediately, as old signatures will no longer validate
The secret looks like: whsec_a1b2c3d4e5f6...
API Routes
Manage webhooks programmatically:
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/webhooks | Create a webhook |
GET | /v1/webhooks | List all webhooks |
GET | /v1/webhooks/:id | Get a webhook |
PUT | /v1/webhooks/:id | Update a webhook |
DELETE | /v1/webhooks/:id | Delete a webhook |
POST | /v1/webhooks/:id/regenerate-secret | Regenerate signing secret |
POST | /v1/webhooks/:id/test | Send a test ping |
GET | /v1/webhooks/:id/events | List recent events |
Webhook Payloads
When a threshold is exceeded, NOPE sends a JSON payload. All payloads share a common structure:
{
// Webhook bodies are flat — there is no "data" envelope.
// These four fields appear on every event:
"event": "evaluate.alert" | "oversight.alert" | "oversight.ingestion.complete" | "test.ping",
"event_id": "evt_abc123def456",
"timestamp": "2025-01-15T10:30:00.000Z",
"api_version": "2025-01"
// ...followed by the event-specific fields shown below.
//
// The webhook config ID is sent in the X-NOPE-Webhook-ID request header,
// alongside X-NOPE-Signature and X-NOPE-Timestamp — not in the body.
} evaluate.alert Payload
Sent when a user evaluation meets your configured risk threshold. Includes the full risk assessment to enable immediate triage:
{
"event": "evaluate.alert",
"event_id": "evt_abc123def456",
"timestamp": "2025-01-15T10:30:00.000Z",
"api_version": "2025-01",
"conversation_id": "conv_67890", // Your ID (if provided)
"user_id": "user_12345", // Your user ID (if provided)
"risk_summary": {
"overall_severity": "high",
"overall_imminence": "urgent",
"primary_domain": "suicide",
"confidence": 0.88,
"primary_concerns": "User expressing active suicidal ideation with specific plan"
},
"domains": [
{ "domain": "suicide", "severity": "high", "imminence": "urgent" }
],
"flags": {
"intimate_partner_violence": null,
"child_safeguarding": null,
"third_party_threat": false
},
"resources_provided": [
{ "name": "988 Suicide & Crisis Lifeline", "type": "crisis_line", "country": "US" }
],
"conversation": {
"included": true, // Only if include_conversation enabled
"message_count": 5,
"latest_user_message": "I can't do this anymore...",
"truncated": false
}
} Use this to:
- Alert human moderators for high-risk conversations
- Log incidents for safety team review
- Trigger automated follow-up workflows
oversight.alert Payload
Sent when AI behavior analysis detects concerning patterns in agent responses:
{
"event": "oversight.alert",
"event_id": "evt_abc123def456",
"timestamp": "2025-01-15T10:30:00.000Z",
"api_version": "2025-01",
"conversation_id": "conv_67890",
"concern": "high",
"trajectory": "worsening",
"summary": "The AI encouraged social isolation and positioned itself as the user's sole support.",
"behaviors": [
{
"code": "dependency_reinforcement",
"name": "Dependency Reinforcement",
"severity": "high",
"category": "boundary_violations"
}
],
"agent_ids": ["agent_companion_v2"],
"platform": "companion_app",
"user_is_minor": false,
"conversation": {
"included": true,
"message_count": 18
}
} Use this to:
- Flag AI responses for safety review
- Identify systematic issues with AI behavior
- Trigger agent retraining or prompt updates
oversight.ingestion.complete Payload
Sent when batch conversation processing completes:
{
"event": "oversight.ingestion.complete",
"event_id": "evt_abc123def456",
"timestamp": "2025-01-15T10:30:00.000Z",
"api_version": "2025-01",
"ingestion_id": "ing_xyz789",
"conversations_total": 1250,
"conversations_processed": 1245,
"conversations_failed": 5,
"concerns": { "none": 1100, "low": 90, "medium": 32, "high": 18, "critical": 5 },
"top_behaviors": [
{ "code": "sycophantic_validation", "name": "Sycophantic Validation", "occurrence_count": 210 }
],
"processing_time_ms": 45230
} test.ping Payload
Sent when testing webhook configuration via the dashboard or API:
{
"event": "test.ping",
"event_id": "evt_abc123def456",
"timestamp": "2025-01-15T10:30:00.000Z",
"api_version": "2025-01",
"message": "Webhook configured successfully"
} HTTP Headers
Every webhook request includes these headers:
| Header | Description |
|---|---|
X-NOPE-Signature | HMAC-SHA256 signature: sha256=<hex> |
X-NOPE-Timestamp | Unix timestamp (seconds) when sent |
X-NOPE-Event | Event type (evaluate.alert, oversight.alert, etc.) |
X-NOPE-Delivery-ID | Unique delivery ID for debugging |
X-NOPE-Webhook-ID | Your webhook configuration ID |
Content-Type | application/json |
User-Agent | NOPE-Webhooks/1.0 |
Verifying Signatures
Always verify webhook signatures to ensure requests came from NOPE and weren't tampered with.
The signature is computed as HMAC-SHA256(secret, timestamp + "." + payload). Verify the exact raw request body bytes before any JSON parser runs. Parsing and re-serializing JSON can change the signed bytes, including for non-ASCII content.
import { Webhook, WebhookSignatureError } from '@nope-net/sdk';
// Web-standard handler. Read bytes before calling request.json().
async function handleNopeWebhook(request: Request): Promise<Response> {
const rawBody = new Uint8Array(await request.arrayBuffer());
try {
const verified = Webhook.verifyRequest(
rawBody,
request.headers,
process.env.NOPE_WEBHOOK_SECRET!
);
console.log(verified.deliveryId, verified.payload.event);
return new Response('OK', { status: 200 });
} catch (error) {
if (error instanceof WebhookSignatureError) {
return new Response('Invalid signature', { status: 401 });
}
throw error;
}
} Security Notes
- Always verify the signature before processing
- Check timestamp freshness to prevent replay attacks (recommended: 5 minute window)
- Use constant-time comparison to prevent timing attacks
- Store your webhook secret securely (treat like an API key)
- Deduplicate retries using
X-NOPE-Delivery-ID
Including Context
To correlate webhook events with your data, include conversation_id and end_user_id in evaluate requests:
const result = await nope.evaluate({
messages: [...],
config: {
country: "US",
conversation_id: "conv_abc123", // Your conversation ID
end_user_id: "user_xyz789" // Your user ID
}
}); These IDs are included in webhook payloads, allowing you to look up the conversation and user in your system.
Responding to Webhooks
Return a 2xx status code within 30 seconds to acknowledge receipt. NOPE retries network failures, 408, 429, and 5xx responses on this schedule:
- Attempt 1: Immediate
- Attempt 2: 1 minute
- Attempt 3: 10 minutes
- Attempt 4: 1 hour
After 4 failed attempts, the event is marked as failed. View delivery history in the dashboard.
Conversation Content
The include_conversation option is product-specific. Evaluate alerts can include the latest user message:
"conversation": {
"included": true,
"message_count": 5,
"latest_user_message": "I can't do this anymore...",
"truncated": false // true if message was >1000 chars
} Oversight alerts include only { included: true, message_count } in their conversation object. They do not include the transcript or latest message. Ingestion-complete and test-ping events have no conversation content.
Privacy Note
Message content is only included if you explicitly enable it. By default, webhooks contain only risk metadata without conversation content.
Example: Slack Alert
app.post('/webhooks/nope', async (req, res) => {
const { payload } = Webhook.verifyRequest(
req.body,
req.headers,
process.env.NOPE_WEBHOOK_SECRET
);
if (payload.event !== 'evaluate.alert') return res.status(200).send('OK');
const { event, risk_summary } = payload;
if (risk_summary.overall_severity === 'high' ||
risk_summary.overall_severity === 'critical') {
await slack.send({
text: `:warning: Risk Alert: ${event}`,
blocks: [{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Severity:* ${risk_summary.overall_severity}
*Imminence:* ${risk_summary.overall_imminence}
*Primary Domain:* ${risk_summary.primary_domain}
*Concerns:* ${risk_summary.primary_concerns}`
}
}]
});
}
res.status(200).send('OK');
}); Testing Webhooks
Use the dashboard or API to send test pings:
curl -X POST https://api.nope.net/v1/webhooks/whk_xxx/test \
-H "Authorization: Bearer YOUR_API_KEY" This sends a test.ping event to verify your endpoint is reachable and signature verification works.
Next Steps
- Evaluation API — Including IDs in requests
- API Reference — Complete endpoint documentation