Webhooks
Real-time event notifications via HTTP callbacks
Webhooks
Webhooks deliver real-time event notifications to your HTTP endpoints. When events occur in Engram (memory stored, compressed, decayed, etc.), Engram sends a POST request to your configured URL with event details.
All webhook payloads are signed with HMAC-SHA256 so you can verify authenticity.
Create Webhook
Register a new webhook endpoint.
POST /v1/webhooks
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint URL to receive events |
events | array[string] | Yes | Event types to subscribe to |
enabled | boolean | No | Whether webhook is active (default: true) |
secret | string | No | Signing secret for HMAC verification. Auto-generated if omitted. |
headers | object | No | Custom headers to include in webhook requests |
timeout_seconds | integer | No | Request timeout (default: 10, max: 30) |
max_retries | integer | No | Maximum retry attempts on failure (default: 3, max: 10) |
retry_backoff_seconds | integer | No | Base backoff interval between retries (default: 60) |
Event Types
| Event | Description |
|---|---|
MEMORY_STORED | New memory created |
MEMORY_RECALLED | Memory retrieved via search or recall |
MEMORY_COMPRESSED | Memory vectors compressed |
MEMORY_DECAYED | Memory importance decayed below threshold |
MEMORY_FORGOTTEN | Memory permanently deleted |
COLLECTION_CREATED | New collection created |
USAGE_THRESHOLD | Usage quota threshold reached (80%, 90%, 100%) |
Code Examples
cURL
curl -X POST https://api.engrammemory.ai/v1/webhooks \
-H "Authorization: Bearer pr_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/engram",
"events": ["MEMORY_STORED", "MEMORY_FORGOTTEN", "USAGE_THRESHOLD"],
"headers": {
"X-Custom-Header": "my-value"
},
"timeout_seconds": 15,
"max_retries": 5,
"retry_backoff_seconds": 120
}'
Python
from engrammemory import Engram
client = Engram(api_key="pr_live_xxxxx")
webhook = client.webhooks.create(
url="https://example.com/webhooks/engram",
events=["MEMORY_STORED", "MEMORY_FORGOTTEN", "USAGE_THRESHOLD"],
headers={"X-Custom-Header": "my-value"},
timeout_seconds=15,
max_retries=5,
retry_backoff_seconds=120,
)
print(f"Webhook ID: {webhook.id}")
print(f"Secret: {webhook.secret}") # Store this for verification
JavaScript
import { Engram } from "engrammemory-ai";
const client = new Engram({ apiKey: "pr_live_xxxxx" });
const webhook = await client.webhooks.create({
url: "https://example.com/webhooks/engram",
events: ["MEMORY_STORED", "MEMORY_FORGOTTEN", "USAGE_THRESHOLD"],
headers: { "X-Custom-Header": "my-value" },
timeoutSeconds: 15,
maxRetries: 5,
retryBackoffSeconds: 120,
});
console.log(`Webhook ID: ${webhook.id}`);
console.log(`Secret: ${webhook.secret}`); // Store this for verification
Response
{
"id": "wh_abc123",
"url": "https://example.com/webhooks/engram",
"events": ["MEMORY_STORED", "MEMORY_FORGOTTEN", "USAGE_THRESHOLD"],
"enabled": true,
"secret": "whsec_7f3a9b2c4d5e6f...",
"headers": {
"X-Custom-Header": "my-value"
},
"timeout_seconds": 15,
"max_retries": 5,
"retry_backoff_seconds": 120,
"created_at": "2026-04-10T12:00:00Z"
}
List Webhooks
List all webhooks for your account.
GET /v1/webhooks
Code Examples
cURL
curl https://api.engrammemory.ai/v1/webhooks \
-H "Authorization: Bearer pr_live_xxxxx"
Python
webhooks = client.webhooks.list()
for wh in webhooks.items:
print(f"{wh.id}: {wh.url} ({len(wh.events)} events)")
JavaScript
const webhooks = await client.webhooks.list();
webhooks.items.forEach((wh) => {
console.log(`${wh.id}: ${wh.url} (${wh.events.length} events)`);
});
Response
{
"items": [
{
"id": "wh_abc123",
"url": "https://example.com/webhooks/engram",
"events": ["MEMORY_STORED", "MEMORY_FORGOTTEN"],
"enabled": true,
"created_at": "2026-04-10T12:00:00Z",
"last_delivery_at": "2026-04-10T14:30:00Z",
"last_delivery_status": "SUCCESS"
}
],
"total": 2
}
Update Webhook
Update an existing webhook configuration.
PATCH /v1/webhooks/{webhook_id}
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
webhook_id | string | Yes | Webhook ID |
Request Body
All fields are optional. Only include fields you want to update.
| Parameter | Type | Description |
|---|---|---|
url | string | Endpoint URL |
events | array[string] | Event types |
enabled | boolean | Active state |
headers | object | Custom headers |
timeout_seconds | integer | Request timeout |
max_retries | integer | Retry attempts |
retry_backoff_seconds | integer | Backoff interval |
Code Examples
cURL
curl -X PATCH https://api.engrammemory.ai/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer pr_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"events": ["MEMORY_STORED", "MEMORY_COMPRESSED"],
"enabled": false
}'
Python
webhook = client.webhooks.update(
"wh_abc123",
events=["MEMORY_STORED", "MEMORY_COMPRESSED"],
enabled=False,
)
JavaScript
const webhook = await client.webhooks.update("wh_abc123", {
events: ["MEMORY_STORED", "MEMORY_COMPRESSED"],
enabled: false,
});
Response
Returns the updated webhook object.
Delete Webhook
Permanently delete a webhook. Pending deliveries will be cancelled.
DELETE /v1/webhooks/{webhook_id}
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
webhook_id | string | Yes | Webhook ID |
Code Examples
cURL
curl -X DELETE https://api.engrammemory.ai/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer pr_live_xxxxx"
Python
client.webhooks.delete("wh_abc123")
JavaScript
await client.webhooks.delete("wh_abc123");
Response
204 No Content
Test Webhook
Send a test event to verify your endpoint is working correctly.
POST /v1/webhooks/{webhook_id}/test
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
webhook_id | string | Yes | Webhook ID |
Code Examples
cURL
curl -X POST https://api.engrammemory.ai/v1/webhooks/wh_abc123/test \
-H "Authorization: Bearer pr_live_xxxxx"
Python
result = client.webhooks.test("wh_abc123")
print(f"Status: {result.status_code}")
print(f"Response time: {result.response_time_ms} ms")
JavaScript
const result = await client.webhooks.test("wh_abc123");
console.log(`Status: ${result.statusCode}`);
console.log(`Response time: ${result.responseTimeMs} ms`);
Response
{
"delivery_id": "del_test_xyz",
"status_code": 200,
"response_time_ms": 145,
"response_body": "OK",
"success": true,
"event_type": "TEST"
}
Webhook Analytics
Get performance statistics for a specific webhook.
GET /v1/webhooks/{webhook_id}/analytics
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
webhook_id | string | Yes | Webhook ID |
Code Examples
cURL
curl https://api.engrammemory.ai/v1/webhooks/wh_abc123/analytics \
-H "Authorization: Bearer pr_live_xxxxx"
Python
analytics = client.webhooks.analytics("wh_abc123")
print(f"Success rate: {analytics.success_rate_pct}%")
print(f"Avg response time: {analytics.avg_response_time_ms} ms")
JavaScript
const analytics = await client.webhooks.analytics("wh_abc123");
console.log(`Success rate: ${analytics.successRatePct}%`);
console.log(`Avg response time: ${analytics.avgResponseTimeMs} ms`);
Response
{
"webhook_id": "wh_abc123",
"total_deliveries": 12450,
"successful_deliveries": 12380,
"failed_deliveries": 70,
"success_rate_pct": 99.44,
"avg_response_time_ms": 132,
"p95_response_time_ms": 340,
"p99_response_time_ms": 780,
"events_breakdown": {
"MEMORY_STORED": 8200,
"MEMORY_FORGOTTEN": 3100,
"USAGE_THRESHOLD": 1150
},
"period": "last_30_days"
}
Delivery History
Get the delivery history for a specific webhook.
GET /v1/webhooks/{webhook_id}/deliveries
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
webhook_id | string | Yes | Webhook ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
page_size | integer | No | Results per page (default: 20, max: 100) |
status | string | No | Filter by SUCCESS or FAILED |
event_type | string | No | Filter by event type |
Code Examples
cURL
curl "https://api.engrammemory.ai/v1/webhooks/wh_abc123/deliveries?page=1&status=FAILED" \
-H "Authorization: Bearer pr_live_xxxxx"
Python
deliveries = client.webhooks.deliveries("wh_abc123", status="FAILED", page=1)
for d in deliveries.items:
print(f"{d.id}: {d.event_type} — {d.status_code} ({d.error})")
JavaScript
const deliveries = await client.webhooks.deliveries("wh_abc123", {
status: "FAILED",
page: 1,
});
deliveries.items.forEach((d) => {
console.log(`${d.id}: ${d.eventType} — ${d.statusCode} (${d.error})`);
});
Response
{
"items": [
{
"id": "del_abc123",
"event_type": "MEMORY_STORED",
"status": "SUCCESS",
"status_code": 200,
"response_time_ms": 120,
"attempt": 1,
"max_attempts": 5,
"request_body": "{\"event\":\"MEMORY_STORED\",\"data\":{...}}",
"response_body": "OK",
"error": null,
"delivered_at": "2026-04-10T14:30:00Z"
},
{
"id": "del_def456",
"event_type": "MEMORY_FORGOTTEN",
"status": "FAILED",
"status_code": 500,
"response_time_ms": 2340,
"attempt": 3,
"max_attempts": 5,
"request_body": "{\"event\":\"MEMORY_FORGOTTEN\",\"data\":{...}}",
"response_body": "Internal Server Error",
"error": "Server returned 500",
"delivered_at": "2026-04-10T14:25:00Z",
"next_retry_at": "2026-04-10T14:33:00Z"
}
],
"total": 70,
"page": 1,
"page_size": 20,
"total_pages": 4
}
HMAC Signature Verification
Every webhook delivery includes an X-Engram-Signature header containing an HMAC-SHA256 signature. Use your webhook secret to verify the payload was sent by Engram.
Header Format
X-Engram-Signature: sha256=<hex-encoded-hmac>
X-Engram-Delivery-Id: del_abc123
X-Engram-Event: MEMORY_STORED
X-Engram-Timestamp: 1712750400
Verification Examples
Python
import hmac
import hashlib
def verify_webhook(payload_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
payload_body,
hashlib.sha256,
).hexdigest()
received = signature_header.replace("sha256=", "")
return hmac.compare_digest(expected, received)
# In your webhook handler:
# verify_webhook(request.body, request.headers["X-Engram-Signature"], "whsec_...")
JavaScript
import crypto from "crypto";
function verifyWebhook(payloadBody, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payloadBody, "utf-8")
.digest("hex");
const received = signatureHeader.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}
// In your webhook handler:
// verifyWebhook(req.body, req.headers["x-engram-signature"], "whsec_...");
Retry Logic
Failed deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | retry_backoff_seconds (default: 60s) |
| 3 | retry_backoff_seconds * 2 (120s) |
| 4 | retry_backoff_seconds * 4 (240s) |
| 5 | retry_backoff_seconds * 8 (480s) |
A delivery is considered failed when:
- Response status code is 4xx or 5xx
- Connection timeout exceeded
- DNS resolution failure
- TLS handshake failure
After all retries are exhausted, the delivery is marked as FAILED permanently. Check the delivery history endpoint to monitor failures.
Webhook Payload Format
All webhook payloads follow this structure:
{
"id": "evt_abc123",
"event": "MEMORY_STORED",
"timestamp": "2026-04-10T14:30:00Z",
"data": {
"memory_id": "mem_xyz789",
"content": "The stored memory content...",
"user_id": "user123",
"metadata": {
"category": "documentation",
"importance": 0.8
}
}
}
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
INVALID_URL | 400 | URL is not valid HTTPS |
INVALID_EVENTS | 400 | Events array is empty or contains invalid event types |
INVALID_TIMEOUT | 400 | Timeout out of range (1-30 seconds) |
WEBHOOK_NOT_FOUND | 404 | Webhook ID does not exist |
WEBHOOK_LIMIT_EXCEEDED | 409 | Maximum webhooks per account reached (default: 20) |
WEBHOOK_URL_UNREACHABLE | 422 | URL failed connectivity check during creation |
RATE_LIMIT_EXCEEDED | 429 | Too many webhook management requests |