Your AI agent just learned something important about a customer. Maybe it extracted a new entity from a conversation. Maybe it detected a contradiction with something it remembered last week. Maybe a critical memory just crossed the decay threshold and is about to expire.
How would you know?
Without webhooks, you wouldn't — not until your agent made a mistake. With 0Latency's webhook system, you get real-time notifications for every meaningful memory event. This guide walks you through setting them up, the event types available, and practical integration patterns for Slack, logging, and audit trails.
Why Memory Webhooks Matter
Most memory systems are black boxes. Data goes in, data comes out, and you hope the middle part works correctly. That's fine for prototyping. It's not fine for production.
In production, you need to know:
- What your agent is learning — is it extracting the right entities? Building correct relationships?
- When memories conflict — a customer said they use React last month but mentioned Vue today. Which is current?
- When critical context expires — a support ticket's context is decaying. Should someone follow up?
- Compliance and audit trails — regulated industries need to track what an agent knows and when it learned it.
Webhooks turn your memory layer from a black box into an observable system. They're the difference between "I think the agent is working" and "I know exactly what the agent knows."
Registering a Webhook
Setting up a webhook takes a single API call. You specify a URL, the events you care about, and optionally filter by agent.
curl -X POST https://api.0latency.ai/docs (webhook endpoints - see API docs) \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/memory",
"events": [
"memory.extraction",
"memory.contradiction",
"memory.decay_threshold"
],
"agent_id": "support-bot",
"secret": "whsec_your_signing_secret"
}'
Response:
{
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/memory",
"events": ["memory.extraction", "memory.contradiction", "memory.decay_threshold"],
"agent_id": "support-bot",
"status": "active",
"created_at": "2026-03-30T12:00:00Z"
}
Security: Every webhook delivery includes an X-0Latency-Signature header — an HMAC-SHA256 hash of the payload using your signing secret. Always verify this before processing events.
Event Types
0Latency fires webhooks for five categories of memory events. Each delivers a structured JSON payload with full context.
memory.extraction Entity Extraction
Fired when the memory engine extracts entities, relationships, or structured data from a /store call. Tells you exactly what your agent learned.
{
"event": "memory.extraction",
"timestamp": "2026-03-30T14:23:01Z",
"agent_id": "support-bot",
"memory_id": "mem_xyz789",
"data": {
"entities": [
{ "name": "Sarah Chen", "type": "person", "confidence": 0.94 },
{ "name": "Acme Corp", "type": "organization", "confidence": 0.98 }
],
"relationships": [
{ "from": "Sarah Chen", "relation": "works_at", "to": "Acme Corp", "confidence": 0.91 }
],
"sentiment": { "overall": "positive", "score": 0.72 }
}
}
memory.contradiction Contradiction Detected
Fired when new information conflicts with existing memories. Critical for maintaining data accuracy and flagging situations that may need human review.
{
"event": "memory.contradiction",
"timestamp": "2026-03-30T15:10:44Z",
"agent_id": "support-bot",
"data": {
"new_memory": {
"id": "mem_new456",
"content": "Customer uses Vue.js for their frontend"
},
"conflicting_memory": {
"id": "mem_old123",
"content": "Customer uses React for their frontend",
"stored_at": "2026-02-15T09:30:00Z"
},
"resolution": "newer_preferred",
"confidence_delta": -0.23,
"requires_review": false
}
}
memory.decay_threshold Decay Threshold
Fired when a memory's confidence score drops below a configured threshold. Useful for triggering follow-ups or re-confirmation workflows.
{
"event": "memory.decay_threshold",
"timestamp": "2026-03-30T16:00:00Z",
"agent_id": "support-bot",
"data": {
"memory_id": "mem_old789",
"content": "Customer's contract renews in Q2 2026",
"current_confidence": 0.32,
"threshold": 0.40,
"last_reinforced": "2026-01-10T11:00:00Z",
"days_since_reinforcement": 79
}
}
memory.relationship Relationship Created
Fired when the graph memory engine creates or updates a relationship between entities. Track how your agent's knowledge graph evolves.
{
"event": "memory.relationship",
"timestamp": "2026-03-30T14:23:02Z",
"agent_id": "support-bot",
"data": {
"from": { "name": "Sarah Chen", "type": "person" },
"relation": "manages",
"to": { "name": "Project Atlas", "type": "project" },
"confidence": 0.87,
"source_memory": "mem_xyz789",
"is_new": true
}
}
memory.sentiment_shift Sentiment Shift
Fired when an entity's aggregated sentiment changes significantly. Detect when a customer is becoming frustrated or a project is trending negative.
{
"event": "memory.sentiment_shift",
"timestamp": "2026-03-30T17:45:00Z",
"agent_id": "support-bot",
"data": {
"entity": "Acme Corp",
"previous_sentiment": { "label": "positive", "score": 0.68 },
"current_sentiment": { "label": "negative", "score": -0.41 },
"shift_magnitude": 1.09,
"contributing_memories": ["mem_a1", "mem_a2", "mem_a3"],
"window": "7_days"
}
}
Verifying Webhook Signatures
Every webhook delivery is signed. Here's how to verify it in your handler:
const crypto = require('crypto');
function verifyWebhook(req, res, next) {
const signature = req.headers['x-0latency-signature'];
const timestamp = req.headers['x-0latency-timestamp'];
const body = JSON.stringify(req.body);
// Reject if timestamp is older than 5 minutes (replay protection)
const age = Date.now() - new Date(timestamp).getTime();
if (age > 300000) {
return res.status(401).json({ error: 'Timestamp too old' });
}
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${body}`)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature), Buffer.from(expected)
)) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
app.post('/webhooks/memory', verifyWebhook, (req, res) => {
// Process the event
handleMemoryEvent(req.body);
res.status(200).json({ received: true });
});
import hmac, hashlib, time
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['WEBHOOK_SECRET']
@app.route('/webhooks/memory', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-0Latency-Signature')
timestamp = request.headers.get('X-0Latency-Timestamp')
body = request.get_data(as_text=True)
# Verify timestamp freshness
ts = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
if (datetime.now(timezone.utc) - ts).total_seconds() > 300:
return jsonify(error='Timestamp too old'), 401
# Verify signature
expected = hmac.new(
WEBHOOK_SECRET.encode(),
f"{timestamp}.{body}".encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return jsonify(error='Invalid signature'), 401
event = request.json
handle_memory_event(event)
return jsonify(received=True), 200
Integration Pattern: Slack Alerts
One of the most common webhook integrations: send memory events to a Slack channel so your team can monitor what your agent is learning in real time.
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL;
async function handleMemoryEvent(event) {
const blocks = [];
switch (event.event) {
case 'memory.contradiction':
blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: `⚠️ *Contradiction Detected* — \`${event.agent_id}\`\n` +
`> New: ${event.data.new_memory.content}\n` +
`> Old: ${event.data.conflicting_memory.content}\n` +
`Resolution: \`${event.data.resolution}\``
}
});
break;
case 'memory.decay_threshold':
blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: `🕐 *Memory Decaying* — \`${event.agent_id}\`\n` +
`> ${event.data.content}\n` +
`Confidence: ${(event.data.current_confidence * 100).toFixed(0)}% ` +
`(${event.data.days_since_reinforcement} days stale)`
}
});
break;
case 'memory.sentiment_shift':
const emoji = event.data.current_sentiment.score > 0 ? '📈' : '📉';
blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: `${emoji} *Sentiment Shift* — ${event.data.entity}\n` +
`${event.data.previous_sentiment.label} → ` +
`*${event.data.current_sentiment.label}* ` +
`(magnitude: ${event.data.shift_magnitude.toFixed(2)})`
}
});
break;
}
if (blocks.length > 0) {
await fetch(SLACK_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ blocks })
});
}
}
This gives your team a live feed of the most important memory events — contradictions that might need human review, memories that are going stale, and sentiment shifts that could indicate customer churn risk.
Integration Pattern: Structured Logging
For production observability, pipe memory events into your existing logging infrastructure. Here's how to send events to a structured logging service:
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
format: format.combine(format.timestamp(), format.json()),
transports: [
new transports.Http({
host: 'http-intake.logs.datadoghq.com',
path: `/api/v2/logs?dd-api-key=${process.env.DD_API_KEY}`,
ssl: true
})
]
});
async function handleMemoryEvent(event) {
logger.info('memory_event', {
event_type: event.event,
agent_id: event.agent_id,
timestamp: event.timestamp,
memory_id: event.data.memory_id || null,
entities_count: event.data.entities?.length || 0,
relationships_count: event.data.relationships?.length || 0,
confidence: event.data.current_confidence || null,
sentiment: event.data.current_sentiment?.label || null,
// Custom tags for Datadog dashboards
ddtags: `agent:${event.agent_id},event:${event.event}`
});
}
With structured logs, you can build dashboards tracking extraction rates, contradiction frequency, average memory confidence, and sentiment trends — all the metrics you need to know your agent's memory is healthy.
Integration Pattern: Audit Trails
For compliance-heavy industries (healthcare, finance, legal), you need an immutable record of everything your agent knows and when it learned it. Webhooks handle this directly:
// Store every memory event for compliance
async function handleMemoryEvent(event) {
await pool.query(`
INSERT INTO memory_audit_log (
event_type, agent_id, event_timestamp,
memory_id, payload, received_at
) VALUES ($1, $2, $3, $4, $5, NOW())
`, [
event.event,
event.agent_id,
event.timestamp,
event.data.memory_id || event.data.new_memory?.id || null,
JSON.stringify(event.data)
]);
// Flag contradictions for human review
if (event.event === 'memory.contradiction') {
await pool.query(`
INSERT INTO contradiction_reviews (
agent_id, new_memory_id, old_memory_id,
resolution, status, created_at
) VALUES ($1, $2, $3, $4, 'pending', NOW())
`, [
event.agent_id,
event.data.new_memory.id,
event.data.conflicting_memory.id,
event.data.resolution
]);
}
}
This gives you a complete, queryable history of your agent's learning process — essential for audits, debugging, and understanding how your agent's knowledge evolved over time.
Managing Webhooks
The full webhook management API lets you list, update, test, and delete webhooks:
# List all webhooks
curl https://api.0latency.ai/docs (webhook endpoints - see API docs) \
-H "X-API-Key: YOUR_API_KEY"
# Test a webhook (sends a test event)
curl -X POST https://api.0latency.ai/docs (webhook endpoints - see API docs)/wh_abc123/test \
-H "X-API-Key: YOUR_API_KEY"
# Update events or URL
curl -X PATCH https://api.0latency.ai/docs (webhook endpoints - see API docs)/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "events": ["memory.contradiction"] }'
# Pause a webhook
curl -X PATCH https://api.0latency.ai/docs (webhook endpoints - see API docs)/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "paused" }'
# Delete a webhook
curl -X DELETE https://api.0latency.ai/docs (webhook endpoints - see API docs)/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY"
Delivery Guarantees and Retry Logic
0Latency webhooks use at-least-once delivery with exponential backoff:
- First attempt: Immediate on event trigger
- Retry 1: 30 seconds
- Retry 2: 2 minutes
- Retry 3: 15 minutes
- Retry 4: 1 hour
- Retry 5: 4 hours (final attempt)
A delivery is considered successful on any 2xx response. If all retries fail, the event is logged and available via the events API for manual retrieval.
Idempotency: Every webhook payload includes a unique event_id. Use this to deduplicate in your handler, since at-least-once delivery means you may receive the same event twice during retries.
Best Practices
- Respond fast. Return a
200immediately, then process asynchronously. If your handler takes more than 10 seconds, the delivery will time out and retry. - Always verify signatures. Never process unsigned payloads. This protects against spoofed events.
- Filter events at registration. Only subscribe to events you'll act on. Less noise means fewer wasted compute cycles.
- Use the test endpoint. Before deploying, send test events to verify your handler works correctly.
- Monitor your endpoint. If 0Latency can't reach your URL, events queue up. Set up uptime monitoring on your webhook receiver.
For more on building observable AI memory systems, check out our guide on temporal memory intelligence and the full pricing comparison with Mem0.
Frequently Asked Questions
agent_id field in webhook registration is optional. If provided, you'll only receive events for that agent. If omitted, you'll receive events for all agents under your account. You can also register multiple webhooks with different agent filters.
memory.decay_threshold event fires. You can set different thresholds for different agents.
POST /webhooks/{id}/replay with a time range filter. This is useful for backfilling a new integration or debugging.
Start building with memory webhooks
Full webhook support on every plan. Set up real-time memory notifications in minutes.
Get your free API key →