Webhooks

On SynapseOrch Cloud, have Synapse POST a signed event to your URL when a knowledge base's document is indexed, partly indexed or failed, or a base is rebuilt.

A webhook endpoint is a URL of yours that SynapseOrch Cloud calls when something happens in your workspace. Each event is one POST, signed with a secret only you and Synapse hold.

Webhooks are a SynapseOrch Cloud feature, managed through /api/v1/webhooks with an API key or a connected app; there is no screen for them yet. A self-hosted install has none — read a document's status with GET /api/v1/knowledge/documents/{doc_id}.

These are not the webhook_url a run or chat request can carry, which calls back once when that run ends — see the API overview. Each endpoint's fields and answers are in the reference, starting at List webhook endpoints.

Events

EventWhen
knowledge.document.indexedA document is searchable
knowledge.document.partialA document is searchable, with something left out
knowledge.document.failedA document could not be indexed
knowledge.base.reindexedA base switched to a rebuilt index

An endpoint subscribes by pattern: an event's name, a family — knowledge.* — or * for everything, now and later. Nothing in between: knowledge.document.* is refused with a 422, so name the document events you want. Without events, an endpoint gets knowledge.*.

Endpoints

MethodPath
GET/api/v1/webhooksThe workspace's endpoints
POST/api/v1/webhooksAdd one — 201, with its secret
GET/api/v1/webhooks/{id}One endpoint
PATCH/api/v1/webhooks/{id}Change its url or events, or switch it off and on with active
DELETE/api/v1/webhooks/{id}Delete it and its delivery log; a delivery still queued is not sent — 204
POST/api/v1/webhooks/{id}/testSend it a signed webhook.test event — 202
GET/api/v1/webhooks/{id}/deliveriesIts deliveries, newest first
POST/api/v1/webhooks/{id}/deliveries/{delivery_id}/redeliverSend one again — 202
curl -X POST https://api.synapseorch.com/api/v1/webhooks \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/synapse", "events": ["knowledge.document.indexed", "knowledge.document.failed", "knowledge.base.reindexed"]}'

The answer carries secret — whsec_… — once; it is never shown again, so store it then. A workspace has up to 10 endpoints, each with up to 20 patterns. The URL must be https and on the public internet; a plain http one is a 422, url must be an https URL. Managing endpoints takes the builder role, or an app granted read to list them and run to change them.

Each endpoint reports active, and when it switched itself off, disabled_reason and disabled_at; consecutive_failures counts deliveries that failed for good since the last that arrived.

A delivery

POST /hooks/synapse HTTP/1.1
Content-Type: application/json
User-Agent: Synapse-Webhook/1.0
X-Synapse-Timestamp: 1759571049
X-Synapse-Signature: sha256=5c0d…
X-Synapse-Signature-V2: sha256=9f1e…

{"id": "kev_…", "type": "knowledge.document.indexed", "created_at": "2026-10-04T09:44:09Z",
 "data": {"kb_id": "kb_…", "doc_id": "kdoc_…", "path": "contracts/msa-globex.pdf",
          "status": "indexed", "previous_status": "indexing", "status_reason": null,
          "version": 2, "sha256": "…", "chunks": 41, "pages": 12, "warnings_count": 0,
          "est_usd": 0.00031, "snapshot_id": "ksnap_…"}}

Every event has the same envelope — id, type, created_at and data. A document's data says which base and document, its new and previous status and why, its version and content hash, what was indexed and what it cost; a knowledge.base.reindexed names the new snapshot_id. A webhook.test carries {"endpoint_id": …}.

Verifying it

Every delivery carries two signatures, keyed with the endpoint's secret:

  • X-Synapse-Signature-V2 is sha256= and the hex HMAC-SHA256 of the timestamp, a dot, and the raw body: "{X-Synapse-Timestamp}.{body}". It covers when the delivery was sent, so you can refuse an old one: accept a timestamp within 5 minutes of your clock. Use this one.
  • X-Synapse-Signature is the HMAC-SHA256 of the body alone, kept for receivers written before V2.

Compute it over the bytes you received, before parsing them, and compare in constant time:

import hashlib, hmac, time

def verify(secret: str, body: bytes, timestamp: str, signature_v2: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False  # older than five minutes: refuse it
    signed = timestamp.encode() + b"." + body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_v2)
import crypto from "node:crypto";

function verify(secret, rawBody, timestamp, signatureV2) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // older than five minutes
  const signed = Buffer.concat([Buffer.from(timestamp + "."), Buffer.from(rawBody)]);
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(signed).digest("hex");
  return signatureV2.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureV2));
}

Each attempt is signed afresh, with its own timestamp. Keep the event ids you have handled and ignore one you have seen: a retry carries the same id.

Answering

Answer with any 2xx, quickly; do the work afterwards. A 5xx, 408 or 429, a timeout or a connection failure is retried — after 10 seconds, a minute, 5 minutes and 30 minutes, five attempts in all. Any other 4xx fails the delivery at once.

Delivery is at least once: an event can arrive twice, which the id check above handles. It is also best effort: in rare cases — a server stopping at the wrong moment — an event is never sent. When it matters that you have every document's final state, read the document: GET /api/v1/knowledge/documents/{doc_id}.

The delivery log

GET /api/v1/webhooks/{id}/deliveries lists an endpoint's deliveries for the last 30 days, newest first — up to 100 at a time (limit, default 50), with before set to the last id seen for the next page. Each shows the event, its state (queued, delivered or failed), the number of attempts, the last status code or error, and the body that was sent. Redeliver sends a delivery again with the same body and event id, to wherever the endpoint points now.

Test sends a signed webhook.test event to check your receiver end to end.

Switched off when it keeps failing

An endpoint whose deliveries fail 20 times in a row, over more than a day, is switched off: it receives no events, and the workspace's owners and admins are told in the notification bell and by email — Webhook switched off. Once your receiver answers again, switch it back on:

curl -X PATCH https://api.synapseorch.com/api/v1/webhooks/{id} \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"active": true}'

Switching it on clears the run of failures. While it is off, Test and Redeliver are refused.

On this page