Agent Memory
Platform

Webhooks

Receive signed HTTP notifications when memories are added, updated, deleted or consolidated — and verify, de-duplicate and retry them correctly.

Webhooks notify your service when memory in a workspace changes, so you can sync a search index, audit changes or trigger downstream work without polling.

Events

EventFires when
addA memory was appended.
updateA memory's content was updated.
deleteA memory was deleted.
categorizeA run was consolidated.
testYou pressed Test delivery in the console. memoryId is null.

A subscription chooses any subset of add, update, delete and categorize. Internal derivations — facts promoted during consolidation, for example — do not emit one event each; consolidation emits a single categorize event per run.

Managing subscriptions

Subscriptions are per workspace and are managed in the web console under Webhooks:

  • Create — give it a name, an HTTPS URL and the events. The signing secret (whsec_…) is shown once.
  • Rotate secret — issues a new secret, shown once. For 24 hours afterwards every delivery is signed with both the new and the previous secret.
  • Disable / enable — disabling pauses queued deliveries (they wait and are re-offered); enabling resumes them. Deleting a subscription discards its queued deliveries.
  • Deliveries — every attempt with its status, HTTP code, response snippet and next retry.
  • Requeue dead — re-queues deliveries that exhausted their retries (up to 100 per action), with the attempt counter reset.

The target URL must be HTTPS and resolve only to public IP addresses; loopback, private, link-local, CGNAT and cloud-metadata addresses are rejected. Creating webhooks requires a plan that includes them.

Delivery format

Each delivery is an HTTP POST with a JSON body and three headers:

HeaderContents
X-AgentMemory-Signaturet=<unix-seconds>,v1=<hex>[,v1=<hex>] — see verification.
X-AgentMemory-EventThe event type.
X-AgentMemory-DeliveryThe delivery ID (GUID). The same across retries of one delivery.
{
  "version": "1",
  "event": "add",
  "eventId": "0d9c2a4e-7b31-4f8a-9c65-2e1d4b8a6f30",
  "memoryId": "8b0c1c0e-6c7d-4a51-9d2e-1f3a5b7c9d0e",
  "tenantId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "workspaceId": "a1b2c3d4-e5f6-4a51-9d2e-0e02b2c3d479",
  "applicationId": null,
  "summary": "…",
  "occurredAt": "2026-08-31T12:34:56.789+00:00"
}
FieldNotes
versionPayload schema version. Branch on it; it changes only if the shape changes incompatibly. New fields may be added without a version bump.
eventSame as the X-AgentMemory-Event header.
eventIdUnique per source event and shared by every subscription's delivery of it — use it to de-duplicate across several endpoints.
memoryIdThe memory concerned; null for test and categorize.
applicationIdThe application scope of the change; null for workspace-wide changes.
summaryA short, redacted description. The memory content itself is never sent — fetch it with GET /v1/memory/{id} if you need it.
occurredAtWhen the change happened.

Verifying signatures

The signature is HMAC-SHA256(secret, "{t}.{rawBody}"), hex-encoded in lowercase, where t is the timestamp from the header. Use the entire secret string, including the whsec_ prefix, as the HMAC key.

Read the raw request body bytes, before any JSON parsing.

Parse t and every v1 entry from X-AgentMemory-Signature. There is one v1 normally and two during the 24 hours after a secret rotation (new secret first).

Compute the expected signature and compare it with each v1 using a constant-time comparison. Accept if any matches.

Reject the delivery if t is more than 300 seconds away from your clock. The timestamp is inside the MAC, so it cannot be forged.

Skip deliveries whose X-AgentMemory-Delivery ID you have already processed, and answer 2xx quickly — the sender times out after 10 seconds. Do heavy work asynchronously.

import crypto from 'node:crypto'
import express from 'express'

const app = express()
const SECRET = process.env.AGENTMEMORY_WEBHOOK_SECRET // whsec_…
const TOLERANCE_SECONDS = 300
const seen = new Set() // use Redis or a database with a 24h TTL in production

app.post('/hook', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-AgentMemory-Signature') ?? ''
  const match = /^t=(\d+)((?:,v1=[0-9a-f]{64})+)$/.exec(header)
  if (!match) return res.status(400).end()

  const t = match[1]
  const signatures = [...match[2].matchAll(/v1=([0-9a-f]{64})/g)].map(m => m[1])
  if (Math.abs(Date.now() / 1000 - Number(t)) > TOLERANCE_SECONDS) return res.status(400).end()

  const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${req.body.toString('utf8')}`).digest('hex')
  if (!signatures.some(s => crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(s))))
    return res.status(401).end()

  const deliveryId = req.get('X-AgentMemory-Delivery')
  if (seen.has(deliveryId)) return res.status(200).end()
  seen.add(deliveryId)

  const event = JSON.parse(req.body)
  if (event.version !== '1') return res.status(200).end() // unknown schema: acknowledge and log
  // handle event.event / event.memoryId
  res.status(200).end()
})

Retries

Any response other than 2xx — or a timeout or connection error — is a failed attempt. Deliveries are retried with backoff of roughly 1 minute, 5 minutes, 30 minutes and 2 hours (each delay varies by ±20%). After 5 attempts a delivery is marked dead and is not retried automatically; requeue it from the console once your endpoint is fixed.

Retries re-send the identical body with the same X-AgentMemory-Delivery ID and a fresh signature timestamp, so de-duplicating on the delivery ID (and on eventId across endpoints) makes processing exactly-once from your side.

The sender never follows redirects — answer the final URL directly — and connects only to the IP address it validated when resolving your hostname.

Self-hosting settings

KeyDefaultEffect
Webhooks:MaxAttempts5Attempts before a delivery is marked dead.
Webhooks:TimeoutSeconds10Per-attempt timeout.
Webhooks:DispatchIntervalSeconds5How often the dispatcher looks for due deliveries.
Webhooks:SecretRotationWindowHours24How long the previous secret keeps signing after a rotation.
retention.webhookDeliveryDays (workspace setting)30How long delivery attempts are kept.

On this page