How to Build a One-Off Invoice Q&A Workflow With a Transient Collection

Embed a single uploaded invoice into a transient collection, query it in the same run to pull out structured fields, and let Spojit discard the collection automatically when the run finishes.

What This Integration Does

Invoices arrive as PDFs that no two vendors format the same way. Rather than maintaining brittle text-position rules, this workflow embeds one invoice document at a time and then asks plain-language questions against it: who issued it, what is the invoice number, the total, the due date, and the line items. Because the answers come from a semantic query rather than fixed coordinates, the same workflow handles wildly different layouts. You get a clean, structured JSON object you can forward to your accounting system, post for approval, or store anywhere.

The run model is a single self-contained pass. A Webhook trigger receives one invoice (as base64 file content), a Knowledge node in Embed mode writes that document into a Transient collection, and a second Knowledge node in Query mode reads it back inside the same run. Transient collections are created per run, shared across the nodes in that run, and cleaned up automatically on completion, so nothing accumulates between invoices and there is no archive to manage. Re-running with the same invoice produces a fresh transient collection every time, so there is no carryover or duplicate-document state to reason about.

Prerequisites

  • A workspace on Spojit with permission to create workflows and use the Knowledge node.
  • The built-in http connector (no connection or auth needed; it runs in-process) if you want to forward results to an external API. The Knowledge node itself needs no connection.
  • A way to send the invoice to the webhook as base64. Most senders post a JSON body like { "filename": "invoice-1042.pdf", "content": "<base64>" }. If your source delivers raw multipart files, convert to base64 before posting.
  • A signing connection for the webhook (scheme Spojit, Shopify, GitHub, Slack, or Custom) so the trigger can verify inbound requests by HMAC.
  • A sample invoice PDF to test with.

Step 1: Add a Webhook trigger that accepts the invoice

Create a new workflow and set the Trigger node type to Webhook. Choose a signing connection so inbound requests are verified by HMAC, then copy the generated workflow URL. The sender posts a JSON body and the trigger exposes the parsed body as {{ input }}. Design the body so the file content and a name are both present:

POST https://your-workflow-url
Content-Type: application/json

{
  "filename": "invoice-1042.pdf",
  "documentType": "PDF",
  "content": "JVBERi0xLjQKJ..."
}

The webhook returns 202 with an executionId immediately, so this pattern is fire-and-forget for the caller. If you instead want the structured answer returned synchronously to the caller, you will add a Response node at the end (see Step 5).

Step 2: Embed the invoice into a Transient collection

Add a Knowledge node and set its mode to Embed. In the Collection dropdown choose Transient: this auto-creates a one-run collection, so you do not set a File Name or pick an Embedding Model. Configure the remaining fields:

  • Document Type: PDF (or map it from {{ input.documentType }} if your senders vary). The node also supports Word, Excel, CSV, Images via OCR, and more.
  • Document Input: {{ input.content }} (the base64 string from the webhook body).
  • Output Variable: name it embedResult. After the node runs, {{ embedResult }} holds the chunk count and metadata, which is useful for confirming the document was actually processed.

Because the collection is transient, you do not need a unique file name and there is no overwrite prompt: each run gets its own isolated collection.

Step 3: Query the same Transient collection for structured fields

Add a second Knowledge node, this time in Query mode, and select Transient in its Collection dropdown so it reads the document embedded in Step 2 within this same run. Configure:

  • Prompt: a plain-language instruction, for example Extract the invoice number, vendor name, invoice date, due date, currency, subtotal, tax, total, and every line item with description, quantity, and unit price.
  • Model: pick the AI model that synthesizes the answer.
  • Result Count: leave at the default 5 for a single short invoice; raise it for multi-page invoices so more chunks feed the answer.
  • Response Schema: supply a JSON schema to force a structured result instead of prose. This is what makes the output reliable to consume downstream.
  • Output Variable: name it invoice.

A Response Schema for the prompt above might look like this:

{
  "type": "object",
  "properties": {
    "invoiceNumber": { "type": "string" },
    "vendor":        { "type": "string" },
    "invoiceDate":   { "type": "string" },
    "dueDate":       { "type": "string" },
    "currency":      { "type": "string" },
    "subtotal":      { "type": "number" },
    "tax":           { "type": "number" },
    "total":         { "type": "number" },
    "lineItems": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": { "type": "string" },
          "quantity":    { "type": "number" },
          "unitPrice":   { "type": "number" }
        }
      }
    }
  },
  "required": ["invoiceNumber", "vendor", "total"]
}

After this node, {{ invoice.total }}, {{ invoice.invoiceNumber }}, and {{ invoice.lineItems }} are available to later steps.

Step 4: Forward the extracted fields with the http connector

If you want to push the result to an external service (an accounting API, an internal endpoint, a ledger), add a Connector node in Direct mode, pick the http connector, and choose the http-post tool. Map the request body from the query output:

{
  "invoiceNumber": "{{ invoice.invoiceNumber }}",
  "vendor":        "{{ invoice.vendor }}",
  "total":         {{ invoice.total }},
  "currency":      "{{ invoice.currency }}",
  "dueDate":       "{{ invoice.dueDate }}",
  "lineItems":     {{ invoice.lineItems }}
}

Direct mode is deterministic and costs no AI credits, so it is the right choice for a single predictable call. Set the target URL and any auth headers your endpoint requires in the tool inputs. If you do not need to forward anywhere, skip this step.

Step 5: Return the result (optional) and verify cleanup

If the caller waits on the response, add a Response node as the final step and return {{ invoice }} so the structured object goes straight back to whoever posted the invoice. If you are running fire-and-forget instead, add a Send Email node to mail yourself a summary, drawing the subject from {{ invoice.invoiceNumber }} and the body from the extracted totals.

There is no cleanup step to build: when the run completes, Spojit removes the transient collection automatically. You can confirm in the execution history that both Knowledge nodes ran and that {{ embedResult }} reported a non-zero chunk count, which proves the document embedded before the query read it.

Tips

  • Keep the Embed node before the Query node in sequence. The transient collection is shared across nodes in the run, but the query can only find chunks that have already been written.
  • Tighten the Response Schema with required fields and numeric types so totals come back as numbers, not strings, ready for math or comparisons downstream.
  • For multi-page or dense invoices, raise Result Count so more chunks reach the model. For a one-page invoice the default of 5 is usually plenty.
  • Use Miraxa, the intelligent layer across your automation, to scaffold this quickly: try "Build a workflow with a Webhook trigger, a Knowledge node in Embed mode using a Transient collection, and a Knowledge node in Query mode against the same Transient collection." Then fine-tune the prompt and schema in the properties panel.

Common Pitfalls

  • Choosing a persistent collection by mistake. If you pick a named collection instead of Transient, every run appends a document and the collection grows without bound. For one-off extraction, always select Transient.
  • Mismatched document content. The Document Input must be base64. If the sender posts a raw or URL-encoded payload, the embed step will not produce usable chunks. Confirm {{ embedResult }} shows a chunk count above zero.
  • Wrong Document Type. Setting the type to Plain Text for a PDF (or vice versa) yields garbled embeddings. Match the Document Type to the real file, or map it from a trusted field in the request body.
  • Webhook replays. The webhook can fire twice if the sender retries. Each retry creates a brand-new run with its own transient collection, so results are not corrupted, but you may post the same invoice twice downstream. Enable event-id dedup on the trigger if your sender provides an event header.

Testing

Start with one known invoice. Post it to the webhook URL with a tool such as curl, then open the execution in history and check three things in order: the Embed node's {{ embedResult }} chunk count is greater than zero, the Query node's {{ invoice }} matches the schema, and the fields are correct against the paper invoice. Only after a single invoice extracts cleanly should you point a real feed at the webhook. Because each run is isolated and self-cleaning, you can re-test freely without leaving any state behind.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.