How to Build a Webhook API That Returns Cited Answers From Your Docs
Build a synchronous Spojit webhook that accepts a question, searches a persistent Knowledge collection, and returns a structured JSON answer with source citations to the caller.
What This Integration Does
This tutorial turns your document archive into a question-answering API. A caller (a help widget, an internal tool, or another service) posts a question to a Spojit webhook URL. The workflow runs a Knowledge node in Query mode against a persistent collection of your documents, forces the answer into a fixed JSON shape with a Response Schema, and hands that JSON straight back to the caller through a Response node. The result is a small retrieval-augmented (RAG) endpoint you own, where every answer is grounded in your own content and carries the source files it came from.
The run model is synchronous request/response. The Webhook trigger receives an HTTP POST, the workflow executes once per request, and the Response node returns the body to the same caller before the connection closes. Nothing is written back to the collection during a query, so the workflow is read-only and safe to call repeatedly: the same question against an unchanged collection returns the same grounded answer. The collection itself is built and maintained separately (uploaded in the Knowledge section or embedded by another workflow), so this API can be called from the moment your documents are embedded.
Prerequisites
- A persistent Knowledge collection in your workspace that already contains your documents (created via Knowledge -> New Collection, then Upload Document -> Upload & Embed). Note its name and the embedding model chosen at creation.
- Documents showing status
READYin the collection's document table, so they are searchable. - A signing connection to verify the webhook (schemes: Spojit, Shopify, GitHub, Slack, or Custom). For a simple internal API the Spojit scheme is a good default. See the guide on adding connections below.
- Permission to create workflows in the workspace, and a way to send a test HTTP POST (curl, Postman, or your calling app).
Step 1: Add a Webhook trigger
Create a new workflow and set the trigger node's Trigger Type to Webhook. Pick your signing connection so incoming requests are verified by HMAC before the workflow runs. Spojit generates the workflow's webhook URL; copy it for later. A webhook trigger normally returns 202 with an executionId, but because this workflow ends in a Response node, the caller instead waits for and receives the structured answer you build. The parsed request body is available downstream as {{ input }}. Design your callers to POST a JSON body like this:
{
"question": "What is our refund window for damaged items?"
}
So the question text is reachable as {{ input.question }}.
Step 2: Validate the incoming question
Add a Condition node right after the trigger to reject empty or malformed requests before spending an AI query. Check that {{ input.question }} is present and non-empty. Route the false branch to a Response node that returns an error payload so callers get a clear message instead of a generic failure:
{
"answer": null,
"sources": [],
"error": "Missing required field: question"
}
Send the true branch on to the Knowledge query. This keeps your API predictable and avoids querying the collection with an empty prompt.
Step 3: Query the Knowledge collection with a Response Schema
Add a Knowledge node and set its mode to Query. Configure the fields:
- Collection: select your persistent collection (not Transient, since the documents live across runs).
- Prompt: pass the caller's question, for example
Answer this question using only the provided documents: {{ input.question }}. - Model: choose the AI model that synthesizes the answer from the retrieved chunks.
- Result Count: how many chunks to retrieve (default
5); raise it for broad questions, lower it for tighter, faster answers. - Response Schema: supply a JSON schema so the node returns structured output instead of free text. This is what makes the endpoint machine-readable.
- Output Variable: name it, for example
kb.
A Response Schema that captures both the answer and its citations:
{
"type": "object",
"properties": {
"answer": {
"type": "string",
"description": "A concise answer grounded only in the retrieved documents"
},
"confident": {
"type": "boolean",
"description": "True if the documents clearly support the answer"
},
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"quote": { "type": "string" }
}
}
}
},
"required": ["answer", "confident", "sources"]
}
The structured result is then available as {{ kb.answer }}, {{ kb.confident }}, and {{ kb.sources }}.
Step 4: Shape the API response body
Add a Transform node to assemble the exact JSON contract your callers expect, independent of the Knowledge node's internal field layout. Map the schema output into a clean public shape, for example:
{
"answer": "{{ kb.answer }}",
"sources": "{{ kb.sources }}",
"confident": "{{ kb.confident }}",
"error": null
}
Name this node's output payload. Centralizing the response shape here means you can change the Knowledge prompt or schema later without breaking your API contract.
Step 5: Return the answer with a Response node
Add a Response node as the final step on the success path and return {{ payload }} to the caller. Because the workflow started from a Webhook trigger and reaches a Response node, the caller's HTTP request stays open until this node runs and receives the JSON you built. The caller gets back a body like:
{
"answer": "Damaged items can be returned within 30 days of delivery for a full refund.",
"sources": [
{
"title": "returns-policy.pdf",
"quote": "Items arriving damaged are eligible for a full refund within 30 days."
}
],
"confident": true,
"error": null
}
Make sure every branch of the workflow (the validation failure path from Step 2 and the success path) terminates in a Response node, so no caller is ever left waiting without a reply.
Step 6: Handle low-confidence answers gracefully
Add a Condition node between the Knowledge query and the final Response to branch on {{ kb.confident }}. When the documents do not support a confident answer, return a fallback through a separate Response node instead of a possibly invented answer:
{
"answer": "I could not find a confident answer in the available documents.",
"sources": [],
"confident": false,
"error": null
}
This protects callers from acting on weak results and makes the "no answer" case explicit and easy to detect on the client side.
Tips
- Keep Result Count modest (3 to 5) for a snappy API; more chunks mean more context to synthesize and a slower, costlier response.
- Ask Miraxa, the intelligent layer across your automation, to scaffold the flow with a prompt like "Build a webhook workflow that queries my Knowledge collection with a Response Schema and returns the answer through a Response node," then fine-tune fields in the properties panel.
- In the Response Schema, request a short
quoteper source so callers can show the exact supporting text, not just a filename. - If you maintain the collection from a second workflow that embeds new documents over time, this query API picks up new content automatically: query and embed are decoupled.
Common Pitfalls
- No Response node on a branch. Any path that ends without a Response node leaves a synchronous caller hanging or returning the default
202instead of your answer. Terminate every branch. - Querying an empty or PROCESSING collection. If documents are not yet
READY, the query returns thin or empty results. Confirm status in the collection's document table first. - Embedding model mismatch. A collection is fixed to the embedding model chosen at creation. Always embed and query the same collection with that model; mixing models degrades retrieval.
- Trusting free-text output. Without a Response Schema, the answer comes back as prose that is hard to parse. Always set the schema so callers receive reliable JSON.
- Skipping signature verification. A webhook without a signing connection is open to anyone with the URL. Always attach a signing connection and have callers sign their requests.
Testing
Before exposing the endpoint, test against a small, known collection. Upload one or two documents with answers you already know, wait for READY status, then POST a question whose answer lives in those files using curl or Postman against the webhook URL (with a valid signature). Confirm the returned JSON matches your Response Schema, that sources point at the right files, and that {{ kb.confident }} is true for in-scope questions. Then POST an off-topic question and verify the low-confidence fallback from Step 6 fires and that confident is false. Use the execution log to inspect what the Knowledge node retrieved if an answer looks wrong. Once both paths behave, point your real caller at the URL.
Learn More
- Knowledge collections and RAG on Spojit
- Knowledge node reference (Embed and Query modes)
- Response node reference for synchronous webhooks
- Trigger node reference, including the Webhook trigger
- Querying Your Knowledge Base
- Using Response Nodes
- Setting Up a Webhook Trigger
- Using Structured Output for Reliable AI Data Extraction
- Using RAG to Answer Questions from Company Documents