Codumentor logo Codumentor

Push-Triggered Ingestion Webhooks

Codumentor can accept push notifications from your Git host and re-ingest the affected repository automatically, so the vector database stays in sync without an admin running codumentor ingest by hand.

The webhook endpoint is designed to work out-of-the-box with Gitea and GitHub push payloads. It also accepts a minimal generic JSON shape — {"repo": "...", "branch": "..."} — so SVN post-commit hooks (or any HTTP client) can drive ingestion without having to fake a git push blob.

How it works

  1. Your Git host sends a push event to POST /ops/webhook/push.
  2. Codumentor verifies the request against a list of configured shared secrets.
  3. The repository block of the payload is matched against your configured repos:. Pushes for repositories not listed in your config are rejected with 404 — the webhook will never clone a repository you have not explicitly opted into.
  4. Codumentor chooses what to do based on the pushed branch: - Push matches the repo's configured branch (or the repo has no branch restriction) → queue a full ingestion: clone/pull the repo and update the vector DB. Response: 202 {"status": "queued"}. - Push is on a different branch → queue a fetch-only refresh: run git fetch --all --tags --prune against the existing clone so origin/<branch> stays current. No re-embedding is performed, but the branch-switching plugin can immediately check that branch out against the latest state. Response: 202 {"status": "fetch_queued"}.
  5. Concurrent pushes for the same repo are coalesced: at most one run is active and at most one is queued. New pushes arriving during a run simply mark "another pass needed" rather than stacking up. When modes mix, full dominates fetch: a full request arriving while a fetch is pending upgrades the pending entry; a fetch request arriving while a full is pending is a no-op (the full run already fetches every ref).
  6. Different repos are serialized within the server process so they never write to the vector DB at the same time. A codumentor ingest running in a separate process (CLI) still claims the cross-process lock; webhook runs that land during that window are dropped and will re-trigger on the next push.

The endpoint answers quickly and never blocks on ingestion work.

Configuration

Add a webhooks block to your codumentor.yaml:

webhooks:
  enabled: true
  secrets:
    - "${CODUMENTOR_WEBHOOK_SECRET}"
    - "${CODUMENTOR_WEBHOOK_SECRET_PREVIOUS}"   # optional, for rotation

repos:
  - url: "https://gitea.example.com/org/my-repo"
    branch: "main"
  - "https://github.com/other-org/their-lib@release"
FieldRequiredDescription
webhooks.enabledyesMust be true for the endpoint to respond. When false, the endpoint returns 404.
webhooks.secretsyesList of accepted shared secrets. The request is accepted if any secret matches. Use environment substitution (${VAR_NAME}) to keep secrets out of the file.

Leaving secrets empty while enabled: true returns 503 — an empty list is treated as a misconfiguration, not as "accept anything".

Rotation. To rotate a secret without downtime, add the new secret to the list first, update the Git host to send the new secret, then remove the old secret on the next config change.

Gitea setup

In your Gitea repository:

  1. Settings → Webhooks → Add Webhook → Gitea.
  2. Target URL: https://codumentor.example.com/ops/webhook/push
  3. HTTP Method: POST
  4. POST Content Type: application/json
  5. Secret: the same value you put in webhooks.secrets.
  6. Trigger On: select "Custom Events…" and enable only Push Events. (Leaving "all events" on is also fine — Codumentor silently returns 204 for events it doesn't care about.)
  7. Save.

Gitea will sign each request with X-Gitea-Signature: <hex HMAC-SHA256 of the body>.

GitHub setup

In your GitHub repository:

  1. Settings → Webhooks → Add webhook.
  2. Payload URL: https://codumentor.example.com/ops/webhook/push
  3. Content type: application/json
  4. Secret: the same value you put in webhooks.secrets.
  5. Which events would you like to trigger this webhook?Just the push event.
  6. Save.

GitHub sends X-Hub-Signature-256: sha256=<hex HMAC-SHA256 of the body>. Codumentor accepts both header styles.

Calling from SVN (post-commit hook)

Subversion has no native webhook system, but the endpoint accepts a minimal generic JSON payload that an SVN post-commit hook can produce in a few lines of shell.

Prerequisites:

  1. The repo must be configured with scm: svn (see the SVN plugin docs). The SVN plugin handles the actual svn checkout / svn update during ingestion.
  2. Add the webhook secret to webhooks.secrets as usual.

Example hooks/post-commit on the SVN server:

#!/bin/bash
SECRET='your-shared-secret'
BODY='{"repo":"myproject","branch":"trunk","event":"push"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')

curl -fsS -X POST https://codumentor.example.com/ops/webhook/push \
  -H 'Content-Type: application/json' \
  -H "X-Webhook-Signature: $SIG" \
  --data "$BODY" >/dev/null

The repo field may be the configured repo's name, its url, or a local path / basename — whatever is easier to produce from the hook. branch is optional; when present it is compared against the configured repo's branch the same way as for git. Omit branch to always do a full ingestion.

What Codumentor does for SVN:

ScenarioAction
branch matches configured branch (or no branch set)Full ingestion. The SVN plugin runs svn update (or svn checkout on first run), then re-embeds changed files.
branch differs from configured branchFetch-only. Runs svn update on the working copy without re-embedding, so the agent's file/shell tools see latest content.

Credentials configured on the SVN repo entry (options.username, options.password) are used automatically for the fetch-only svn update.

Calling from other systems

Any client that can compute HMAC-SHA256 over the raw request body will work. Use either the Gitea-shaped push payload or the generic shape:

# Gitea / GitHub shape (git)
SECRET='your-shared-secret'
BODY='{"ref":"refs/heads/main","repository":{"clone_url":"https://gitea.example.com/org/my-repo.git","ssh_url":"git@gitea.example.com:org/my-repo.git","full_name":"org/my-repo","name":"my-repo"}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')

curl -X POST https://codumentor.example.com/ops/webhook/push \
  -H 'Content-Type: application/json' \
  -H 'X-Gitea-Event: push' \
  -H "X-Gitea-Signature: $SIG" \
  --data "$BODY"

# Generic shape — any repo identifier plus an optional branch
BODY='{"repo":"my-repo","branch":"main"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')

curl -X POST https://codumentor.example.com/ops/webhook/push \
  -H 'Content-Type: application/json' \
  -H "X-Webhook-Signature: $SIG" \
  --data "$BODY"

HMAC headers are interchangeable: X-Gitea-Signature, X-Hub-Signature-256 (accepts the sha256= prefix), and X-Webhook-Signature are all validated the same way.

If you cannot compute an HMAC (e.g., a simple monitoring script), Codumentor also accepts a bearer-token fallback against the same secret list:

curl -X POST https://codumentor.example.com/ops/webhook/push \
  -H 'Content-Type: application/json' \
  -H 'X-Gitea-Event: push' \
  -H "Authorization: Bearer $SECRET" \
  --data "$BODY"

The X-Webhook-Token: $SECRET header is accepted as an equivalent alternative.

Security note. The HMAC path authenticates both the caller and the body contents. The bearer-token path only authenticates the caller. Prefer HMAC when the Git host supports it.

Accepted payload shapes

Gitea / GitHub push (git-only). The endpoint reads repository identification from, in order of preference:

Branch detection reads ref (refs/heads/<branch>). Tag pushes and other refs are ignored.

Generic (works for git, SVN, or anything else):

{"repo": "<name | url | path>", "branch": "<branch>", "event": "push"}

Only repo is required. branch is optional (omit to always do a full ingest). event is optional — any non-push value causes the endpoint to return 204 so the same URL can be wired into a hook that fires for multiple event types.

The repo field is matched against your configured repos by URL, name, local path, or path basename — whichever makes your hook simplest.

A payload missing repository identification (no repository block and no repo field) returns 400.

Response codes

CodeMeaning
202Accepted. Work has been queued. Body is {"status": "queued", "repo": "<name>"} for a full ingestion, or {"status": "fetch_queued", "repo": "<name>"} for a fetch-only refresh on a non-indexed branch.
204Event ignored on purpose (non-push event such as ping).
400Malformed JSON or missing repository identification.
401Missing, invalid, or unmatched signature / token.
404Webhooks disabled, or the pushed repository is not in config.repos.
503Webhooks enabled but secrets list is empty — misconfiguration.

Observability

Webhook activity logs under the codumentor.api.webhook and codumentor.ingestion.webhook_scheduler loggers. Useful messages:

Bump logging.logger_levels.codumentor.api.webhook to DEBUG if you need per-request tracing.

Operational caveats