Skip to content

Webhooks & Event Hooks

CodeNavi emits outbound, metadata-only events for its main lifecycle moments — indexing, crawling, secret detection, chat, and licensing. Use them to drive local automation (freshness pipelines, security/audit feeds, ops alerts) without anything leaving the box.

Two independent delivery channels, both off by default, both safe to run in airgapped / privacy-sensitive deployments:

Channel Setting What happens
HTTP webhook HOOK_URL CodeNavi POSTs the event as JSON to the URL
Local script HOOK_SCRIPT_DIR CodeNavi runs <dir>/<event>.sh with the JSON on stdin

Both can be active at once. Delivery is fire-and-forget on a background thread — a hook can never slow down or break indexing/chat; failures are logged and dropped (no retries, no queue).

Why this is airgap-friendly

  • The script channel needs zero network. reindex.completed.sh runs locally on the box — no socket, no DNS.
  • The HTTP channel can target local infra — a self-hosted Mattermost/Rocket.Chat, Prometheus Alertmanager, a SIEM (Wazuh/Elastic/Splunk), n8n/Node-RED. Point HOOK_URL at http://something.internal/… and the traffic stays on your LAN.
  • Payloads are metadata-only — never code, never secret values. A scanner.blocked event carries the file path and the rule IDs that matched, never the matched secret. So events are safe to forward even in environments that are paranoid about everything else.

Enabling

Set via environment variables, data/settings.json, or the web UI (System → Hooks):

Setting Default Meaning
HOOK_URL (empty) HTTP endpoint to POST events to. Empty = HTTP channel off.
HOOK_SCRIPT_DIR (empty) Directory of <event>.sh scripts. Empty = script channel off.
HOOK_TIMEOUT_SEC 5 Per-POST timeout for HOOK_URL.
HOOK_SCRIPT_TIMEOUT_SEC 30 Per-script timeout before it's killed (independent of HOOK_TIMEOUT_SEC — scripts typically need longer than a webhook POST).

HOOK_URL / HOOK_SCRIPT_DIR apply live when saved via the UI/settings; HOOK_TIMEOUT_SEC is read per call.

The payload envelope

Every event — webhook body or script stdin — has the same JSON shape:

{
  "event": "reindex.completed",
  "data": { "...": "event-specific fields" },
  "ts": 1750650000.123
}
  • event — one of the names below.
  • data — event-specific fields (table).
  • ts — Unix timestamp (float seconds).

Event reference

Event Fires when data fields
reindex.started a reindex sweep begins full (bool), codebases (list)
reindex.completed a reindex sweep ends (encoding done; a VRAM post-step may follow) full, codebases, status (ok|cancelled|failed), encoded (int — chunks re-embedded), cleanup (skip|reset|restart — the post-encode VRAM step1), error (string, only on failed)
reindex.cleanup the post-reindex in-process VRAM reset finishes (only fires for cleanup:"reset") mode (reset), status (ok|failed), codebases, encoded, error (string, only on failed — server then restarts to recover). Await this before resuming queries; on restart the server SIGTERMs instead, so watch for it to come back
reindex_docs.completed PDF/DOCX doc ingestion finishes codebase, ingested, skipped, errors (counts)
crawl.started a web crawl run starts run_id, env, triggered_by
crawl.completed a crawl run finishes successfully run_id, total_urls, total_errors, embedded (bool)
crawl.error a crawl run fails run_id, reason
scanner.blocked a file is quarantined during indexing (secret/threat) file (path), rule_ids (list of matched rule IDs — never the secret value), llm_escalated (bool)
chat.completed a chat turn finishes session_id, codebase, source_type_filter, response_chars (length — not the text), provider, model
tier.limit_warning a reindex exceeds the licensed file cap codebase, files, file_limit, tier

Events not in this list are ignored. The canonical list lives in core/hooks.py (EVENTS).

Script hooks (the airgap-native channel)

When HOOK_SCRIPT_DIR is set, CodeNavi looks for an executable named exactly after the event — scanner.blocked.sh, reindex.completed.sh, etc. — and runs it with the JSON payload on stdin. Contract:

  • Name = <event>.sh (with the dot, e.g. crawl.error.sh). Only the events you write a script for fire; the rest are silent no-ops.
  • Input = the full envelope JSON on stdin.
  • Must be executable (chmod +x).
  • Timeout = 30 s by default, then the script is killed — configurable via HOOK_SCRIPT_TIMEOUT_SEC.
  • Exit code and stdout/stderr are ignored — write to your own log/endpoint, not stdout.

Minimal example (scanner.blocked.sh):

#!/usr/bin/env bash
payload="$(cat)"
file="$(printf '%s' "$payload" | python3 -c "import sys,json;print(json.load(sys.stdin)['data'].get('file',''))")"
mkdir -p /tmp/codenavi-hooks
echo "$(date -Is) secret quarantined in: $file" >> /tmp/codenavi-hooks/security.log

Ready-to-use, offline-by-default examples live in examples/hooks/ in your CodeNavi package.

Use cases

  • Security / compliance audit feed. Forward scanner.blocked to a local SIEM or ticketing system — an auditable record that CodeNavi detected and quarantined a secret, with no secret value in the event. See the security model.
  • Index-freshness automation. React to reindex.completed instead of polling — refresh a dashboard, run an impact report, or release a downstream job once the index is current.
  • Ops alerting. Route crawl.error, a failed reindex.completed, or tier.limit_warning to your alerting stack.
  • Usage metering. Tally chat.completed for internal chargeback or seat reporting (metadata only).

Because the script channel is just "run my script," your team wires CodeNavi into existing internal tooling with a few lines — no vendor-built connectors required.

Operational notes

  • Delivery is best-effort: one attempt, no retry, no persistence. If your receiver is down, the event is dropped (a warning is logged).
  • Hooks run on daemon threads and never block or fail indexing/chat.
  • The only event that can fire in bulk is scanner.blocked (one per quarantined file); in practice quarantined files are rare.

  1. Vendor change reference CN-281. ↩