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
| Event | When |
|---|---|
knowledge.document.indexed | A document is searchable |
knowledge.document.partial | A document is searchable, with something left out |
knowledge.document.failed | A document could not be indexed |
knowledge.base.reindexed | A 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- Verify the signature.
X-Synapse-Signature-V2issha256=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.) - Drop repeats. Delivery is at least once, so an event can arrive twice; its
idis the same each time. - Answer quickly. Any
2xxis 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,408or429, a timeout or a connection failure is tried again after 10 seconds, a minute, 5 minutes and 30 minutes — five attempts in all. Any other4xxfails the delivery at once, so answer401only to a bad signature. - Redeliver.
POST /api/v1/webhooks/{id}/deliveries/{delivery_id}/redeliversends 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