Webhooks

Have SynapseOrch Cloud call your server when a knowledge base's document is indexed or fails: add an endpoint, verify each signed delivery, answer it, and recover when deliveries fail — with worked examples.

Harbor & Pine's accounts system sends every new invoice to Suppliers & invoices as a record. It wants to know when each one is searchable, and to hear at once when one is not. Rather than asking again and again, it gives SynapseOrch Cloud a URL to call: a webhook endpoint. Each time a document in the workspace is indexed, partly indexed or fails, or a base switches to a rebuilt index, Synapse sends one signed POST there.

Webhooks are a SynapseOrch Cloud feature, managed through the API with an API key; there is no screen for them yet. They are not the webhook_url a single run can carry, which calls back once when that run ends — see Be called when it ends.

1. Add an endpoint

Choose the 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, the family knowledge.*, or * for every event, now and later. Without events it gets knowledge.*. Nothing in between: knowledge.document.* is refused with a 422 — 'knowledge.document.*' is not an event: name one (…), a family's (knowledge.*), or '*'.

Register the URL

curl -X POST https://api.synapseorch.com/api/v1/webhooks \
  -H "Authorization: Bearer $SYNAPSE_API_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://accounts.harborpine.example/hooks/synapse",
       "events": ["knowledge.document.indexed", "knowledge.document.partial", "knowledge.document.failed"]}'

The URL must be https and on the public internet: a plain http one is refused, url must be an https URL., and so is a host that does not resolve, url is not allowed: could not resolve … A workspace has up to 10 endpoints, each with up to 20 patterns. Managing them takes a key, or the builder role.

Keep the secret

The 201 answer carries the endpoint's id and its secret, whsec_…, once: it is never shown again. Store it where your receiver reads secrets.

export SYNAPSE_WEBHOOK_SECRET=whsec_...

2. Receive a delivery

Every delivery has the same envelope, signed with the endpoint's secret:

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": "_records/inv-1039.json",
          "status": "indexed", "previous_status": "indexing", "status_reason": null, …}}

A document's data says which base and document, its new and previous status and why, its version, what was indexed and what it cost. A receiver does three things before anything else:

import hashlib
import hmac
import os
import time

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["SYNAPSE_WEBHOOK_SECRET"].encode()
seen = set()  # the event ids already handled: keep them in your database


@app.post("/hooks/synapse")
def synapse_event():
    body = request.get_data()  # the raw bytes, before any parsing
    timestamp = request.headers.get("X-Synapse-Timestamp", "0")
    if abs(time.time() - int(timestamp)) > 300:  # older than five minutes
        abort(401)
    signed = timestamp.encode() + b"." + body
    expected = "sha256=" + hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Synapse-Signature-V2", "")):
        abort(401)
    event = request.get_json()
    if event["id"] in seen:  # delivered twice: already handled
        return "", 204
    seen.add(event["id"])
    if event["type"] == "knowledge.document.failed":
        notify_accounts(event["data"]["path"], event["data"]["status_reason"])  # your own code
    return "", 204
  1. Verify the signature. X-Synapse-Signature-V2 is sha256= and the hex HMAC-SHA256 of "{X-Synapse-Timestamp}.{body}", keyed with the secret, so it covers when the delivery was sent: refuse a timestamp more than five minutes from your clock. Compute it over the bytes you received, before parsing them, and compare in constant time. (X-Synapse-Signature, over the body alone, is kept for older receivers.)
  2. Drop repeats. Delivery is at least once, so an event can arrive twice; its id is the same each time.
  3. Answer quickly. Any 2xx is a delivery. Do slow work afterwards, off the request.

Webhooks → Verifying it has the same check in JavaScript.

3. Test it, then watch it work

Send a test event

curl -X POST https://api.synapseorch.com/api/v1/webhooks/{id}/test \
  -H "Authorization: Bearer $SYNAPSE_API_KEY"

202, and a signed webhook.test event, with {"endpoint_id": …} as its data, arrives at your receiver.

Make something happen

Send a record — the invoice in Adding documents, say — or upload a file to a base. When it is indexed, knowledge.document.indexed arrives, its path _records/inv-1039.json.

Read the delivery log

curl https://api.synapseorch.com/api/v1/webhooks/{id}/deliveries \
  -H "Authorization: Bearer $SYNAPSE_API_KEY"

Each delivery, newest first, for 30 days: its event, its state — queued, delivered or failed — the number of attempts, the last status code or error, and the body that was sent.

4. When deliveries fail

  • Retries. A 5xx, 408 or 429, a timeout or a connection failure is tried again after 10 seconds, a minute, 5 minutes and 30 minutes — five attempts in all. Any other 4xx fails the delivery at once, so answer 401 only to a bad signature.
  • Redeliver. POST /api/v1/webhooks/{id}/deliveries/{delivery_id}/redeliver sends one again, with the same body and event id.
  • Switched off. An endpoint whose deliveries fail 20 times in a row, over more than a day, is switched off, and the workspace's owners and admins hear Webhook switched off in the bell and by email. Once your receiver answers again, switch it back on with PATCH /api/v1/webhooks/{id} and {"active": true}. While it is off, Test and Redeliver are refused.
  • Best effort. In rare cases — a server stopping at the wrong moment — an event is never sent. When you must have every document's final state, read it: GET /api/v1/knowledge/documents/{doc_id}.

Self-hosted

A self-hosted install has no webhooks. Poll a document after sending it, until it rests: GET /api/v1/knowledge/documents/{doc_id} — see Knowing when a document is ready.

Go further

  • Webhooks — every route, the envelope, verifying in JavaScript, the delivery log
  • Knowledge API — records, uploads and documents from your own code
  • Notifications — Webhook switched off and the other events people hear about
  • The public API — runs, their status and the per-run webhook_url

On this page