# Build an n8n Ticket-Triage Workflow and Test Its Failure Paths

> Import a credential-free n8n workflow, run eight ticket-triage fixtures, and inspect review, duplicate, invalid-output and stale-record branches.

- Source: https://www.zarifautomates.com/blog/how-to-use-n8n-for-advanced-ai-workflow-automation
- Published: 2026-09-17
- Updated: 2026-09-17
- Pillar: Automation Workflows
- Tags: agent-course, ai-engineering, practical-guide
- Author: Zarif

---

A workflow that labels one clean ticket is easy to demonstrate. The harder question is what happens when the same event arrives twice, a ticket is missing, or the model returns something your application cannot accept.

This lesson gives you an importable n8n workflow with those cases already built in. Eight synthetic events pass through validation, lookup, a model-output fixture and a review gate. Two become review items. Six are rejected with reasons. Nothing sends a message or changes a ticket.

[Download the inactive workflow JSON](/downloads/agent-course/n8n-ticket-triage-v1.json) or [get the workflow, input fixtures and recorded results together](/downloads/agent-course/n8n-ticket-example-v1.zip).

The workflow was executed locally with **n8n 2.39.7 and Node 24.19.0 on September 17, 2026**. It uses the real n8n execution engine and JavaScript Code nodes. Its model output is synthetic, so these results establish workflow behavior rather than model quality.

## 1. Import it without publishing

If you already use n8n's editor, create a new workflow, open its menu and choose **Import from File**. Select the downloaded JSON, inspect the ten nodes and keep the workflow unpublished. The file has `active: false`, one Manual Trigger, no credentials, no HTTP Request node and no schedule. The [official import guide](https://docs.n8n.io/build/manage-workflows/export-and-import) explains the file-import options.

The Manual Trigger lets you run the example deliberately. Select **Execute Workflow**, then inspect the three terminal nodes: **Review items**, **Rejected events**, and **Rejected proposals**. Their outputs should contain two, two and four items respectively.

For a repeatable local CLI run, use a new empty directory with Node 24 installed. Save the JSON there as `ticket-triage.json`. These macOS/Linux commands install the tested version and give it a separate local state directory:

```bash
node --version
npm init -y
npm install n8n@2.39.7
export N8N_USER_FOLDER="$PWD/n8n-test-state"
export N8N_DIAGNOSTICS_ENABLED=false
export N8N_VERSION_NOTIFICATIONS_ENABLED=false
export N8N_TEMPLATES_ENABLED=false
export N8N_PERSONALIZATION_ENABLED=false
export N8N_RUNNERS_BROKER_LISTEN_ADDRESS=127.0.0.1
export N8N_RUNNERS_BROKER_PORT=15689
npx n8n import:workflow --input=ticket-triage.json
npx n8n execute --id=ZarifTicketLab01 --rawOutput
```

Use that isolated state directory for the exercise: importing a workflow with an ID already present in another database can replace it. If port 15689 is occupied, choose another local port. No public server or tunnel is needed.

The [Server CLI documentation](https://docs.n8n.io/deploy/host-n8n/configure-n8n/use-the-command-line) describes importing a saved workflow and executing it by ID. The tested release rejects the older `execute --file` shortcut, so import first. Startup messages may precede the JSON execution data. This JavaScript-only run also logged that the optional Python runner was unavailable; the JS Task Runner registered and executed the Code nodes successfully.

## 2. Follow the data through the nodes

```text
Manual test → Synthetic events → Validate events → Event valid?
                                                   ├─ false → Rejected events
                                                   └─ true  → Lookup and model fixture
                                                              → Validate proposals
                                                              → Proposal valid?
                                                                 ├─ true  → Review items
                                                                 └─ false → Rejected proposals
```

Every Code node uses **Run Once for All Items**. In the [Code node's all-items mode](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code), `$input.all()` supplies the input items. The transformations return objects with `json` data and retain each item's link to its input through `pairedItem`.

| Node | Configuration to inspect |
| --- | --- |
| Synthetic events | Eight explicit input objects, including failures |
| Validate events | Required field types; integer version; duplicate IDs within this batch |
| Event valid? | Strict Boolean condition on `event_valid`; false output goes to Rejected events |
| Lookup and model fixture | Two synthetic tickets at version 3; controlled good, invalid and malformed model output |
| Validate proposals | Ticket exists; version matches; JSON has exactly one allowed label |
| Proposal valid? | Strict Boolean condition on `proposal_valid` |
| Review items | Emits a proposal with `state: awaiting_review` and `applied: false` |
| Rejected events / proposals | Retain event ID and reason; never mark the change applied |

The lookup and model step is deliberately a fixture. You can inspect every input that reaches it and make it fail without paying for a model call or touching another system.

## 3. Read the eight results

The fixture named `evt-1` appears twice. The first copy is accepted for review; the second is rejected within that execution. The terminal outputs from the recorded run were:

| Event | Case | Result |
| --- | --- | --- |
| `evt-1`, first copy | Valid billing proposal | Awaiting review |
| `evt-1`, second copy | Repeated event ID | `duplicate_event_in_batch` |
| `evt-2` | Missing ticket | `ticket_not_found` |
| `evt-3` | Label `delete-account` | `invalid_label_contract` |
| `evt-4` | Version supplied as a string | `invalid_event` |
| `evt-5` | Valid technical proposal | Awaiting review |
| `evt-6` | Text that is not JSON | `invalid_json` |
| `evt-7` | Version 2 against current version 3 | `stale_version` |

An accepted review item looks like this:

```json
{
  "event_id": "evt-1",
  "ticket_id": "DEMO-42",
  "expected_version": 3,
  "proposed_label": "billing",
  "state": "awaiting_review",
  "applied": false
}
```

There are **eight input events**, **two review items**, **six rejections**, and **zero applied changes**. These are counts for this fixture run, not an accuracy score. A workflow can route every item as designed while the underlying model is still wrong about its labels.

## 4. Change one failure and rerun

Open **Synthetic events** and change `evt-3` from the `invalid-label` fixture to the default case by removing its `fixture` field. Execute the workflow again. You should now see three review items and five rejections.

Next restore the original fixture and change `evt-7` to `expected_version: 3`. Its stale-version rejection should become a review item. Keep a copy of the original export so you can restore the eight-case baseline.

Then inspect **Validate proposals**. Its JSON parser and allowed-label check decide whether an output can proceed. Connecting the model node directly to a write node would skip that check. Keep the validation step visible even after the happy path works.

## 5. Know what must change for a real integration

The example's duplicate detector is a JavaScript `Set` created for one batch. Running the workflow again starts a new set and produces the review items again. This is **not durable deduplication across executions or restarts**. Before processing real deliveries, use a persistent store with a unique event ID and an atomic claim of work; test concurrent deliveries against it.

The review items also exist in execution output, not in a staffed approval queue. To extend the workflow, choose a durable review destination and record the exact proposal, record version, decision, reviewer and expiry. A separate executor should recheck the version when applying an approved change.

Replace the synthetic lookup and model fixture with real nodes only after choosing their credentials and record scope. Preserve the same proposed-label contract and run the failure cases again. Test model behavior separately with representative, permitted data; passing these fixture branches does not establish live model accuracy.

Finally, add the actual provider's webhook verification and a bounded error policy. n8n's [error-handling guide](https://docs.n8n.io/flow-logic/error-handling) covers error workflows, but expected input rejections and unexpected execution failures deserve different handling. Do not retry a completed external write merely because its response was lost.

Your deliverable is the imported workflow, the eight-case baseline and one changed fixture with the expected new counts. Keep it unpublished until you have designed and tested the external boundaries. Continue to the [agent capstone](/blog/ticket-agent-workflow-capstone) to connect the course's components into one inspectable application.

## Related Guides

- [How to Build a Lead Generation Workflow in n8n Step by Step](/blog/how-to-build-lead-gen-workflow-n8n)
- [How to Build a Weekly AI Article Recommendation Workflow](/blog/how-to-build-weekly-ai-article-recommendation-workflow)
- [How to Create an AI Video Production Workflow](/blog/ai-video-production-workflow)

## Continue the course

Lesson 16 of 17.

Previous lesson: [Use Cursor to Build a Ticket Approval Interface You Can Test](https://www.zarifautomates.com/blog/how-to-use-cursor-ai-to-build-web-applications.md).

Next lesson: [Build a Ticket Workflow: Signed Delivery, Evidence and Human Approval](https://www.zarifautomates.com/blog/ticket-agent-workflow-capstone.md).

[Browse available lessons](https://www.zarifautomates.com/blog/pillar/agents-and-ai-engineering#agent-course-heading).
