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:
low,medium,high, orcritical - Include conversation — optionally include the latest message content
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:
{
"event": "evaluate.alert" | "oversight.alert" | "oversight.ingestion.complete" | "test.ping",
"webhook_id": "wh_xxx",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": { /* event-specific payload */ }
} 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",
"webhook_id": "wh_abc123def456",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": {
"request_id": "req_xyz789",
"end_user_id": "user_12345",
"conversation_id": "conv_67890",
"summary": {
"speaker_severity": "high",
"speaker_imminence": "urgent",
"any_third_party_risk": false,
"primary_concerns": "User expressing active suicidal ideation with specific plan"
},
"risks": [
{
"subject": "self",
"subject_confidence": 0.92,
"type": "suicide",
"severity": "high",
"imminence": "urgent",
"confidence": 0.88,
"features": ["active_ideation", "plan_present", "hopelessness"]
}
],
"legal_flags": {
"ipv": null,
"safeguarding_concern": null,
"third_party_threat": null
},
"confidence": 0.88,
"crisis_resources": [
{
"type": "crisis_line",
"name": "988 Suicide & Crisis Lifeline",
"phone": "988",
"is_24_7": true
}
]
}
} 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",
"webhook_id": "wh_abc123def456",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": {
"analysis_id": "oa_xyz789",
"end_user_id": "user_12345",
"agent_id": "agent_companion_v2",
"conversation_summary": {
"concern_level": "high",
"trajectory": "worsening",
"behaviors_detected": [
{
"behavior_id": "romantic_escalation_with_minor",
"severity": "high",
"confidence": 0.91,
"message_indices": [12, 15, 18]
}
]
},
"recommendation": "immediate_human_review"
}
} 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",
"webhook_id": "wh_abc123def456",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": {
"batch_id": "batch_xyz789",
"conversations_processed": 1250,
"conversations_flagged": 23,
"high_concern_count": 5,
"processing_time_ms": 45230,
"status": "completed"
}
} test.ping Payload
Sent when testing webhook configuration via the dashboard or API:
{
"event": "test.ping",
"webhook_id": "wh_abc123def456",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": {
"message": "Webhook endpoint is configured correctly"
}
} 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)
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const payload = JSON.stringify(req.body);
const signature = req.headers['x-nope-signature'];
const timestamp = req.headers['x-nope-timestamp'];
// Check timestamp freshness (prevent replay attacks)
const now = Math.floor(Date.now() / 1000);
const age = now - parseInt(timestamp, 10);
if (Math.abs(age) > 300) { // 5 minutes
throw new Error('Timestamp too old or in future');
}
// Compute expected signature
const message = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
// Verify signature (constant-time comparison)
const received = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
} 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)
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: {
user_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 to acknowledge receipt. NOPE will retry failed deliveries with exponential backoff:
- 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
If you enable include_conversation, webhooks 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
} 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) => {
// Verify signature first
if (!verifyWebhookSignature(req, process.env.NOPE_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { event, risk_summary, domains } = req.body;
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