Webhooks
Receive notifications when events happen in your Hebbrix account. Webhooks let you build integrations that react to memory changes, document processing, and other events.
How Webhooks Work
When an event occurs (like a new memory being created), Hebbrix sends an HTTP POST request to your configured endpoint with details about the event. Your server processes the payload and responds with a 2xx status to acknowledge receipt.
Supported Events
| Field | Type | Description |
|---|---|---|
| memory.created | - | A new memory was created |
| memory.updated | - | A memory was updated |
| memory.deleted | - | A memory was deleted |
| memory.tier_changed | - | A memory was promoted/demoted between tiers |
| document.created | - | A new document was uploaded |
| document.processed | - | Document processing finished (searchable) |
| document.failed | - | Document processing failed |
| document.deleted | - | A document was deleted |
| collection.created | - | A new collection was created |
| collection.updated | - | A collection's metadata or members changed |
| search.completed | - | A hybrid/graph search returned results |
| user.feedback_received | - | User provided explicit feedback on a memory / answer |
| rl.training_started | - | A reinforcement-learning job kicked off |
| rl.training_completed | - | Reinforcement-learning job finished successfully |
| rl.training_failed | - | Reinforcement-learning job failed |
Endpoints
Webhook Payload
Every webhook delivery includes these standard fields:
{
"id": "evt_abc123xyz",
"event_type": "memory.created",
"timestamp": 1705315800,
"data": {
"id": "mem_xyz789",
"content": "User prefers dark mode",
"collection_id": "col_default",
"importance": 0.75,
"created_at": "2024-01-15T10:30:00Z"
},
"idempotency_key": "memory.created:mem_xyz789:550e8400-e29b-41d4-a716-446655440000:wh_abc123"
}timestamp is a Unix epoch integer (seconds). Use idempotency_key to deduplicate; the same key is used for all retry attempts of a single delivery.
Verifying Signatures
X-Webhook-Signature-256 header (format sha256=<hex>) and the signing timestamp in X-Webhook-Timestamp. Verify both using your signing_secret to confirm the request came from Hebbrix.import hmac
import hashlib
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_here"
@app.route("/webhooks/hebbrix", methods=["POST"])
def handle_webhook():
# Get the signature + timestamp from headers
signature = request.headers.get("X-Webhook-Signature-256", "")
timestamp = request.headers.get("X-Webhook-Timestamp")
# Strip the "sha256=" prefix
if signature.startswith("sha256="):
signature = signature[7:]
# Create the signed payload (timestamp.body)
payload = f"{timestamp}.{request.data.decode()}"
# Calculate expected signature
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
# Verify signature matches (timing-safe)
if not hmac.compare_digest(signature, expected):
return "Invalid signature", 401
# Process the event
event = request.json
print(f"Received event: {event['event_type']}")
if event["event_type"] == "memory.created":
handle_memory_created(event["data"])
elif event["event_type"] == "document.processed":
handle_document_processed(event["data"])
return "OK", 200import crypto from 'crypto';
import express from 'express';
const app = express();
const WEBHOOK_SECRET = 'whsec_your_secret_here';
app.post('/webhooks/hebbrix', express.raw({type: 'application/json'}), (req, res) => {
const signatureHeader = (req.headers['x-webhook-signature-256'] || '') as string;
const timestamp = req.headers['x-webhook-timestamp'] as string;
// Strip the "sha256=" prefix
const signature = signatureHeader.startsWith('sha256=')
? signatureHeader.slice(7)
: signatureHeader;
// Create the signed payload (timestamp.body)
const payload = `${timestamp}.${req.body.toString()}`;
// Calculate expected signature
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
// Verify signature matches (timing-safe)
const sigBuf = Buffer.from(signature, 'hex');
const expBuf = Buffer.from(expected, 'hex');
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(req.body.toString());
console.log(`Received event: ${event.event_type}`);
switch (event.event_type) {
case 'memory.created':
handleMemoryCreated(event.data);
break;
case 'document.processed':
handleDocumentProcessed(event.data);
break;
}
res.status(200).send('OK');
});Retry Policy
If your endpoint doesn't respond with a 2xx status (or the request times out / errors at the transport layer), delivery is retried with exponential backoff up to 5 total attempts. Backoff waits 2^attempt seconds, clamped to a minimum of 4 seconds and a maximum of 3600 seconds (1 hour) between attempts.
- Attempt 1: immediate
- Attempt 2: after ~4s
- Attempt 3: after ~8s
- Attempt 4: after ~16s
- Attempt 5: after ~32s
After 5 failed attempts the delivery is moved to the Dead Letter Queue (DLQ) and marked as failed. Each attempt uses the same idempotency_key so your receiver can safely deduplicate.
Best Practices
- Respond Quickly: return a 2xx response within 30 seconds. Process events asynchronously if needed.
- Handle Duplicates: use the event ID to deduplicate. The same event may be delivered multiple times.
- Verify Signatures: always verify the X-Webhook-Signature-256 header to ensure authenticity.
- Use HTTPS: HTTPS recommended; HTTP is accepted for public hosts. Localhost endpoints are rejected for security.
cURL Example
/v1/webhookscurl -X POST "https://api.hebbrix.com/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/hebbrix",
"events": [
"memory.created",
"document.processed"
]
}'