Stack: n8n (self-hosted) · Webhook · OpenRouter (strict JSON) · PostgreSQL · Telegram
One webhook takes incoming requests in any shape — website forms, attached documents, free-text messages — and turns each into a PostgreSQL record plus a lead card sent to the responsible person. The core idea is separating code from the model: routing and ready-made form fields are handled deterministically (by code); AI is used only where it is actually needed — to extract structure from unstructured input.
Published for portfolio review, not for reuse.
All sample data is synthetic (names, contacts, companies, phones are fictional). Prompt texts are not published.
The client (a car-sourcing service) received requests in mixed shapes and qualified them by hand — read, pull out fields, enter them, pass on. That took 1–2 hours per request, with two risks: manual data-entry errors, and losing a request between "read it" and "entered it" under peak load.
A single webhook endpoint in self-hosted n8n. The client POSTs a payload from anywhere (site, CRM, bot); the module fits existing infrastructure through that one webhook.
- Routing by code, not the model. Right after the payload arrives, the request type is decided deterministically: a file link → document; structured form fields → site form; only text → free message; none of these → escalation. No AI here — the shape is known, so a model would only add latency and cost.
- Four branches.
- Form (~0.5s, no AI) — fields are already structured; map them straight to the schema.
- Document (~15s) — download the file, extract PDF text, send to the agent, extract fields, normalize.
- Free text (~13–15s) — straight to the agent, same fields/schema.
- Escalation — anything that matches no rule (broken payload, unknown format) is not lost: the responsible person gets a Telegram alert with the raw body marked "needs manual review".
- Strict JSON output. The agent (OpenRouter, JSON mode) returns a fixed schema — type, priority, contacts, a one-line summary, and case-specific extracted fields. On invalid JSON an automatic corrective call runs; half-baked data is never written.
- Two actions in parallel. Write to PostgreSQL (the source of truth: UUID, time, source, type, priority, contacts, summary, raw payload, extracted fields) and send a readable lead card to Telegram.
portfolio_demo.intake_leads — one row per request. See /schema.
- AI branches cut manual handling from 1–2 hours → ~15 seconds.
- A structured form is processed in ~0.5 seconds — near-zero cost and latency.
- Peak load reached ~80 requests/week; the base module was built in ~1 week.
- Fewer manual extraction errors; no request drops out of the funnel even on a broken payload (Telegram escalation with the original body).
- Add a
confidencefield and auto-escalate low-confidence parses. - Collect wrong parses into a table for prompt tuning / fine-tuning.
- Add an OCR step for scanned documents (current PDF-text extraction won't read scans).
- A log table for escalations to grow the deterministic routing rules over time.
| Path | What |
|---|---|
/workflows |
sanitized n8n workflow JSON |
/schema |
sanitized PostgreSQL DDL + notes |
/assets |
architecture diagram + screenshots |
.env.example |
names of the secrets to configure |
Secrets live in n8n's encrypted credential store, separated from workflow logic — the
exported JSON references credentials by name only. A hard-coded recipient chat id was
moved to MANAGER_CHAT_ID, and inline prompt texts were redacted.