Korely

Events

Webhooks

Get a signed POST the moment a memory is created, a fact is superseded, or an end user is about to hit a quota, so your product reacts in real time instead of polling.

Register an endpoint and Korely delivers an HTTP POST for the events you subscribe to. Each delivery is signed (HMAC-SHA256, Standard Webhooks scheme), retried with exponential backoff, and carries a stable id so you can dedupe. Webhooks are best-effort and fully decoupled, they never block or slow down the add / search calls that trigger them.

Why fact.invalidated is the one that matters. When a new memory contradicts something Korely already knew, the old typed fact is superseded (invalidated, never silently overwritten) and you get pushed the event, so any cache, search index, or downstream prompt you built stays honest. A plain vector store has no typed facts to invalidate, so it has nothing to push. This is the moat, delivered as an event.

Events

EventFires whenData fields
memory.created A memory is added, via POST /v1/memories or a batch import. id, user_id, agent_id
fact.invalidated A typed fact is superseded because a newer memory contradicted it. fact_id, invalidated_by, at
quota.warning A monthly write quota crosses 80% (fired once at the crossing). used, limit, percent

Subscribe to specific events, or use * to receive all of them. The body of every delivery is a JSON envelope: a top-level event string and a data object whose shape depends on the event.

Payloads

{
"event": "memory.created",
"data": {
"id": "mem_8f2c1a",
"user_id": "u_42",
"agent_id": "support-bot"
}
}

Register an endpoint

Webhook endpoints are managed from your Korely dashboard. Add the URL that should receive deliveries (must be http:// or https://), pick the events (or *), and Korely returns a signing secret of the form whsec_.... Store that secret, you need it to verify every delivery, and it is shown so you can rotate it if it ever leaks.

You can register more than one endpoint (for example, one for staging and one for production), and each gets its own secret. Use the dashboard's Send test action to enqueue a sample memory.created delivery and confirm your receiver and signature check work before you depend on real traffic.

Verify the signature

Every delivery carries three headers. Always verify before you trust the body:

HeaderValue
webhook-idUnique id for this delivery. Use it to dedupe replays.
webhook-timestampUnix seconds when Korely signed the delivery.
webhook-signatureThe signature, formatted v1,<base64>.

The signature is an HMAC-SHA256 over the exact string {webhook-id}.{webhook-timestamp}.{raw-body}, keyed by your whsec_... secret (used verbatim as the UTF-8 key, do not base64-decode it), base64-encoded and prefixed with v1,. Compute it over the raw request body, byte-for-byte, before any JSON parsing or re-serialization, re-encoding the JSON will change the bytes and break the check.

import hmac, hashlib, base64
WEBHOOK_SECRET = "whsec_..." # from your dashboard
def verify(headers, raw_body: bytes) -> bool:
msg_id = headers["webhook-id"]
ts = headers["webhook-timestamp"]
sig = headers["webhook-signature"] # "v1,<base64>"
signed_content = f"{msg_id}.{ts}.{raw_body.decode()}".encode("utf-8")
digest = hmac.new(WEBHOOK_SECRET.encode("utf-8"), signed_content, hashlib.sha256).digest()
expected = "v1," + base64.b64encode(digest).decode("ascii")
# constant-time compare
return hmac.compare_digest(expected, sig)

As an extra guard against replay, reject deliveries whose webhook-timestamp is more than a few minutes old.

Delivery & retries

  • Success is any 2xx. Return 200, 299 quickly. Any other status, a timeout, or a connection error counts as a failure and is retried.
  • At-least-once. A delivery can arrive more than once (after a retry or a network hiccup). Dedupe on webhook-id and make your handler idempotent.
  • Exponential backoff. Failed deliveries are retried with a growing delay (doubling, capped at one hour) until the attempt limit is reached, then marked failed.
  • No strict ordering. Retries mean a later event can arrive before an earlier one. Don't assume order, use the data in each event, or re-read from the API if you need the current state.
  • Decoupled by design. Events are queued to an outbox and delivered by a background worker, so a slow or down endpoint never affects the add / search request that produced the event.
  • Respond fast, work async. Acknowledge with a 2xx immediately and do heavy processing in your own queue. The delivery times out after ~10s of read time.

Notes

  • Keep the secret server-side. Anyone with the whsec_ secret can forge a valid signature. Never ship it to a browser or mobile client.
  • One secret per endpoint. Register separate endpoints for staging and production so a leaked staging secret can't sign production traffic.
  • Verify, then parse. Compute the signature over the raw body first; only parse the JSON once the signature matches.
  • Disabled on repeated failure. Endpoints that keep failing accumulate a failure count and stop receiving deliveries until you fix and re-enable them.

Related