Safe House webhooks
React to threats in real-time. Safe House emits webhook events when it blocks a message, quarantines a session, detects a canary trigger, or identifies a cross-agent attack campaign. Route these events to PagerDuty, Slack, your SIEM, or any HTTP endpoint.
Safe House webhooks reuse the existing AIP webhook infrastructure. If you already have AIP webhooks configured, add safe_house.* to your event_types — no new endpoints or secrets required.
Safe House evaluation ──→ Gateway ──→ Signed POST (X-AIP-Signature) ──→ Your Endpoint
│
├── PagerDuty (safe_house.evaluation.block)
├── Slack (safe_house.evaluation.quarantine)
├── SIEM (all safe_house.*)
└── Security on-call (safe_house.canary.triggered)
Event types
| Event | When It Fires | Severity |
|---|
safe_house.evaluation.warn | Safe House flags a turn above the warn threshold but below block | Informational |
safe_house.evaluation.quarantine | Safe House quarantines a turn for human review | High |
safe_house.evaluation.block | Safe House blocks a turn outright | Critical |
safe_house.canary.triggered | A canary credential was used — indicating active exploitation | Critical |
safe_house.session.escalated | Session risk has risen to high across multiple turns | High |
safe_house.campaign.detected | Multiple agents have received similar attacks from the same infrastructure | Critical |
Payload structure
All events share a common envelope. The data object varies by event type.
safe_house.evaluation.block
{
"event": "safe_house.evaluation.block",
"timestamp": "2026-03-30T14:32:11Z",
"agent_id": "mnm-550e8400-e29b-41d4-a716-446655440000",
"session_id": "18a228f3-1234",
"data": {
"verdict": "block",
"quarantine_id": "qid_7f3a9b2c",
"overall_risk": 0.94,
"top_threat": {
"type": "bec_fraud",
"confidence": 0.94,
"reasoning": "Financial action ('wire transfer') · Urgency ('immediately') · Authority ('CEO') · Secrecy ('don't tell')"
},
"detection_layer": "l2",
"review_url": "https://app.mnemom.ai/safe-house/quarantine/qid_7f3a9b2c"
}
}
safe_house.evaluation.quarantine
{
"event": "safe_house.evaluation.quarantine",
"timestamp": "2026-03-30T15:11:04Z",
"agent_id": "mnm-550e8400-e29b-41d4-a716-446655440000",
"session_id": "18a228f3-1234",
"data": {
"verdict": "quarantine",
"quarantine_id": "qid_2a9c4f11",
"overall_risk": 0.71,
"top_threat": {
"type": "indirect_injection",
"confidence": 0.71,
"reasoning": "Imperative instruction structure detected in tool result · References external data destination"
},
"detection_layer": "l1",
"pending_human_review": true,
"review_url": "https://app.mnemom.ai/safe-house/quarantine/qid_2a9c4f11"
}
}
safe_house.canary.triggered
{
"event": "safe_house.canary.triggered",
"timestamp": "2026-03-30T16:00:00Z",
"agent_id": "mnm-550e8400-e29b-41d4-a716-446655440000",
"session_id": "18a228f3-5678",
"data": {
"canary_id": "can_f9e2a01b",
"canary_type": "api_key",
"triggered_by": "inbound_message",
"raw_signal": "Canary credential appeared in inbound turn content",
"action_taken": "session_terminated"
}
}
safe_house.session.escalated
{
"event": "safe_house.session.escalated",
"timestamp": "2026-03-30T16:45:22Z",
"agent_id": "mnm-0b3f2a1c-d4e5-4f60-b7a8-9c0d1e2f3a4b",
"session_id": "a9f34c12-2233",
"data": {
"previous_risk": "medium",
"current_risk": "high",
"escalation_reason": "hijack_attempt",
"turn_count": 7,
"pattern_description": "Benign session pivot — topic and action scope shifted abruptly at turn 6"
}
}
safe_house.campaign.detected
{
"event": "safe_house.campaign.detected",
"timestamp": "2026-03-30T17:22:09Z",
"data": {
"campaign_id": "camp_b3c9d4a1",
"threat_type": "bec_fraud",
"affected_agents": ["mnm-550e8400-e29b-41d4-a716-446655440000", "mnm-0b3f2a1c-d4e5-4f60-b7a8-9c0d1e2f3a4b", "mnm-a4bc5d6e-7f89-4012-b3c4-d5e6f7a8b9c0"],
"agent_count": 3,
"similarity_score": 0.92,
"first_seen": "2026-03-30T16:50:00Z",
"infrastructure_hint": "Similar sender fingerprint and payload structure across agents"
}
}
Signature verification
Every Safe House webhook is signed with HMAC-SHA256, identical to AIP webhooks. The signature header is X-AIP-Signature with value sha256=<hex digest>.
Always verify signatures before acting on a webhook. An unverified webhook can be spoofed.
import crypto from 'crypto';
import { Request, Response } from 'express';
export function handleSafeHouseWebhook(req: Request, res: Response) {
const signature = req.headers['x-aip-signature'] as string;
const secret = process.env.MNEMOM_WEBHOOK_SECRET!;
// Use the raw body — any middleware that parses JSON changes the byte sequence
const body = req.rawBody; // or however your framework exposes raw bytes
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(body);
// safe to process
}
Use timingSafeEqual rather than === for signature comparison. String equality is vulnerable to timing attacks that can be used to brute-force the secret one byte at a time.
Timestamp freshness check (recommended): Reject payloads where timestamp is older than 5 minutes to prevent replay attacks.
const age = Date.now() - new Date(event.timestamp).getTime();
if (age > 5 * 60 * 1000) {
return res.status(400).send('Stale event');
}
Registration
Safe House events use the same webhook registration as AIP webhooks. Add safe_house.* to your event_types:
# Add Safe House events to an existing webhook endpoint
curl -X PATCH https://api.mnemom.ai/v1/orgs/{org_id}/webhooks/{endpoint_id} \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_types": [
"integrity.violation",
"drift.detected",
"safe_house.evaluation.block",
"safe_house.evaluation.quarantine",
"safe_house.evaluation.warn",
"safe_house.canary.triggered",
"safe_house.session.escalated",
"safe_house.campaign.detected"
]
}'
Or register a dedicated Safe House endpoint:
curl -X POST https://api.mnemom.ai/v1/orgs/{org_id}/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/safe-house",
"description": "Safe House security alerts",
"event_types": [
"safe_house.evaluation.block",
"safe_house.evaluation.quarantine",
"safe_house.canary.triggered",
"safe_house.campaign.detected"
]
}'
Retry logic
Webhook delivery follows the same retry policy as AIP webhooks. If your endpoint returns a non-2xx status, Mnemom retries at increasing intervals:
| Attempt | Delay |
|---|
| 1 (initial) | Immediate |
| 2 | 1 000 ms |
| 3 | 5 000 ms |
| 4 | 15 000 ms |
After 4 failed attempts, the delivery is marked failed in aip_webhook_deliveries. You can redeliver manually:
curl -X POST https://api.mnemom.ai/v1/orgs/{org_id}/webhooks/deliveries/{delivery_id}/redeliver \
-H "Authorization: Bearer $TOKEN"
Integration patterns
Route safe_house.evaluation.block to on-call
High-confidence blocks warrant immediate human attention. Page your security on-call directly.
if (event.event === 'safe_house.evaluation.block' && event.data.overall_risk > 0.9) {
await pagerduty.createIncident({
title: `Safe House block: ${event.data.top_threat.type} on agent ${event.agent_id}`,
severity: 'critical',
body: event.data.top_threat.reasoning,
links: [{ href: event.data.review_url, text: 'Review in Mnemom' }],
});
}
Route safe_house.evaluation.quarantine to human reviewer
Quarantined items need human review before the session can continue. Post to Slack with the review link.
if (event.event === 'safe_house.evaluation.quarantine') {
await slack.postMessage({
channel: '#security-review',
text: [
`*Safe House Quarantine* — Agent \`${event.agent_id}\``,
`Threat: \`${event.data.top_threat.type}\` (${Math.round(event.data.top_threat.confidence * 100)}% confidence)`,
`Reasoning: ${event.data.top_threat.reasoning}`,
`<${event.data.review_url}|Review and release →>`,
].join('\n'),
});
}
Treat safe_house.canary.triggered as a security incident
A canary trigger means an attacker has gained access to a credential planted specifically to detect exploitation. Escalate immediately.
if (event.event === 'safe_house.canary.triggered') {
await incidentResponse.openP0({
title: `Canary credential triggered — active exploitation suspected`,
agentId: event.agent_id,
sessionId: event.session_id,
canaryId: event.data.canary_id,
});
// Optionally: contain the agent immediately
await fetch(`https://api.mnemom.ai/v1/orgs/${ORG_ID}/agents/${event.agent_id}/pause`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.MNEMOM_API_KEY}` },
body: JSON.stringify({ reason: 'Canary credential triggered — automated containment' }),
});
}
Log all Safe House events to your SIEM
app.post('/webhooks/safe-house', verifySignature, (req, res) => {
const event = req.body;
siem.ingest({
source: 'mnemom-safe-house',
event_type: event.event,
agent_id: event.agent_id,
session_id: event.session_id,
timestamp: event.timestamp,
payload: event.data,
});
res.status(200).send('ok');
});
Testing your integration
Send a test event to your endpoint without waiting for a real threat:
curl -X POST https://api.mnemom.ai/v1/orgs/{org_id}/webhooks/{endpoint_id}/test \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"event_type": "safe_house.evaluation.block"}'
The test payload uses synthetic data but is signed with your real signing secret — your signature verification code runs against a real payload.
See also