Skip to main content
Mnemom webhook events are operator-actionable signals. Every event the platform emits — whether a billing event (subscription.status_changed), a Safe House signal (integrity.violation), or a sideband detection firing (sideband.coherence.fired) — carries the same seven contractual guarantees. This page is the rationale for those guarantees and how to verify them. The full event catalog is at Webhook Event Catalog.

The seven invariants

1. Stable name

Every event is identified by a hierarchical, dotted name (<context>.<axis>[.<event>]). The name is stable across versions — if the underlying mechanism changes, the existing name continues to work; the new mechanism gets a new name. The name space is a closed, versioned enum, published in full at the Webhook Event Catalog. A new event is added to the catalog atomically with its schema and its emission code — the platform’s own CI fails a change that adds one without the other.

2. Versioned JSON Schema

Every event has a JSON Schema, rendered live on docs.mnemom.ai/api-reference/webhook-events and exported as OpenAPI for client generation. Payload evolution is additive — new fields can be added without bumping the event name. Breaking changes bump the catalog version (date-based, mirroring X-Mnemom-Version).

3. Example payload

Every event in the catalog ships with an example payload — the canonical reference for client implementations. Test delivery end-to-end without waiting for a real event with mnemom webhooks trigger <org_id> <endpoint_id>, which fires a synthetic test event through the full delivery pipeline to that endpoint.

4. Idempotency key

Every event carries a stable id field in the form evt-<random>. Receivers can safely dedupe on this id — Mnemom guarantees the same id will not be issued for two different events. When Mnemom retries delivery (per the retry policy below), the same id flows through every attempt. Your receiver can store seen ids and short-circuit re-emissions without losing semantic correctness.

5. HMAC signature

Every delivery is signed HMAC-SHA256 over ${X-Webhook-Timestamp}.${rawBody} using the per-endpoint signing secret, sent as X-Webhook-Signature: v1=<hex> (Stripe convention), with a 5-minute replay tolerance. See Signature verification for runnable verification code in Node and Python.

6. Retry policy

Mnemom retries failed deliveries on this schedule (in seconds): [10, 30, 120, 600, 3600] (5 retries, 6 attempts total). Receivers that return any 2xx within 30 seconds are considered successful. 5xx and connection failures are retried; 4xx (other than 408/429) are NOT retried — they indicate a permanent receiver-side error. After 100 consecutive failures across all events to an endpoint, Mnemom auto-disables the endpoint and emails the org owner. The endpoint stays disabled until manually re-enabled. The first-attempt timeout is 30 seconds (mirroring Stripe / Cloudflare receiver-budget conventions). Receivers with database writes or downstream fan-out should write the event to a queue first and ack immediately.

7. Replay path

Every emitted event is durably stored in webhook_events and can be replayed via POST /v1/orgs/:org_id/webhooks/events/:event_id/replay. Replay re-fans-out the event to all currently-subscribed active endpoints (or to a caller-specified subset via endpoint_ids[] in the request body). Replay differs from redeliver:
  • replay rebuilds the fan-out from the canonical event row — picks up endpoints that have been added or re-subscribed since the original emission.
  • redeliver retries one specific delivery row.
Both shapes ship — Stripe-equivalent ergonomics. See the webhook event catalog for the full API surface. The replay endpoint requires an Idempotency-Key header; reuse with the same key returns the cached delivery list (does NOT mint a second fan-out). Cached replays carry an Idempotent-Replay: true response header.

Surface separation invariant

Mnemom webhook events live exclusively on the operator surface. Operators are humans, dashboards, paging systems, automated systems acting outside an agent’s request loop. Agent-actionable signals (per-turn, conversation-bound, where the agent has a same-turn lever) belong to pending_advisories only and have no webhook fan-out. This is enforced at three layers:
  1. Schema metadata. Every catalog entry declares x-mnemom-surface: operator-actionable. Anything else fails the CI lint.
  2. Producer-layer separation. pending_advisories accepts only runtime.* + manual.* source values; governance_signals accepts sideband.* + future protection.* / posture.*. The two surfaces are mutually exclusive.
  3. CI assertion. Every emitted event has a negative-assertion test confirming that when the event fires, the agent’s verify-turn prompt remains byte-clean (no leak from operator surface to agent prompt).

Scope: which deliveries this contract covers

This contract governs the org webhook subscription system: the closed set of catalog events, delivered to endpoints you register at POST /v1/orgs/:org_id/webhooks. It is the delivery mechanism for sideband.*.fired, recipe.candidate.created, billing events, and the rest of the catalog — see that page’s note on which detection-recipe lifecycle events stay internal to Mnemom today. Governance signal notification destinations (governance.signal.fired and its lifecycle siblings) are a separate, purpose-built delivery path configured per-org under /v1/orgs/:org_id/governance/notification-destinations — not an entry in this catalog. It uses its own signature header (X-Mnemom-Signature: sha256=<hex> over the raw body) and its own delivery semantics. Don’t assume this page’s retry schedule, idempotency key format, or X-Webhook-Signature scheme apply there.

What the contract is not

  • Not at-most-once delivery. Mnemom delivers at-least-once, with idempotency keys for receiver-side dedup. Build your receiver to handle a small number of duplicates.
  • Not strict ordering. Events are emitted in the order they occur, but delivery to a slow receiver may arrive out of order if a retry races a fresh delivery. Use the event payload’s created_at for canonical ordering.
  • Not exactly-once semantics. Combined with idempotency keys, you can build exactly-once semantics on the receiver — but the wire contract is at-least-once.
  • Not customer-tunable retry. The retry schedule is platform-wide. If you need different retry behavior, dead-letter to a queue at your receiver and process there.

See also