{
  "name": "Raise Zendesk tickets from a website support chatbot using OpenAI",
  "nodes": [
    {
      "parameters": {
        "content": "## Raise Zendesk tickets from a website support chatbot using OpenAI\n\nFor support teams that want a chatbot to resolve simple questions first and raise a Zendesk ticket only when a human is needed.\n\n### How it works\n1. A webhook receives the widget message and checks a shared secret.\n2. An OpenAI agent answers from your help content and only offers a ticket when it cannot resolve the issue or the visitor asks for a person.\n3. The create ticket tool validates the email and creates a ticket with subject, description, priority and tags for the chatbot and session.\n4. The agent tells the visitor a person will follow up by email.\n\n### Setup\n- [ ] Edit **Chatbot settings**: company name, your knowledge text and a random `webhook_secret`.\n- [ ] Add your OpenAI credential to **GPT-4.1 mini model**.\n- [ ] Copy each tool section below the main flow into its own new workflow, save it, then select it in the matching tool node.\n- [ ] In each tool workflow, add your Zendesk credential and fill in **Tool settings**.\n- [ ] Test with a POST to the webhook Test URL (the n8n chat panel needs a Chat Trigger). Then activate and add the production URL to your widget, with the same secret as `metadata.webhook_secret`.\n\n### Requirements\nn8n, an OpenAI API key and a Zendesk API token with your subdomain.\n\n### Customization tips\nChange priorities, tags or the ticket fields, or tighten the prompt on when to raise a ticket.",
        "width": 480,
        "height": 1104
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        0
      ],
      "id": "c94c8367-eb0a-4f40-a74c-bdc3cc183d2d",
      "name": "Template overview"
    },
    {
      "parameters": {
        "content": "## 1. Receive and verify the message\nThe webhook takes the widget message. Requests without your secret are rejected.",
        "width": 944,
        "height": 432,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "0be2f330-ffbd-499c-bf4e-0e631f952eea",
      "name": "Section 1: Receive and verify the message"
    },
    {
      "parameters": {
        "content": "## 2. Answer with the AI agent\nThe agent answers from your settings, remembers the session and calls its tools when needed.",
        "width": 704,
        "height": 512,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        528
      ],
      "id": "d5228502-a461-40ce-b2ab-7ae723891254",
      "name": "Section 2: Answer with the AI agent"
    },
    {
      "parameters": {
        "content": "## 3. Tool: create_ticket (copy to its own workflow)\nCopy these nodes into a new workflow and save it. Then select that workflow in the create_ticket tool node.",
        "width": 1120,
        "height": 576,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        1136
      ],
      "id": "aacab60c-8d0e-47b3-80ff-b3a3b56eaa64",
      "name": "Section 3: Tool: create_ticket (copy to its own workflow)"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "zendesk-support-chatbot",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook",
      "name": "Chat webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        64,
        112
      ],
      "webhookId": "zendesk-support-chatbot-webhook"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "company_name",
              "value": "Your Company",
              "type": "string"
            },
            {
              "id": "c2",
              "name": "knowledge",
              "value": "PASTE YOUR HELP CENTER / FAQ COPY HERE.\n\nThe bot answers only from this text before offering to raise a ticket.",
              "type": "string"
            },
            {
              "id": "c3",
              "name": "webhook_secret",
              "value": "CHANGE_ME_TO_A_RANDOM_STRING",
              "type": "string"
            },
            {
              "id": "c4",
              "name": "message",
              "value": "={{ $json.body.chatInput || $json.body.message || '' }}",
              "type": "string"
            },
            {
              "id": "c5",
              "name": "session_id",
              "value": "={{ $json.body.sessionId || 'no-session' }}",
              "type": "string"
            },
            {
              "id": "c6",
              "name": "page_url",
              "value": "={{ $json.body.metadata?.page_url || '' }}",
              "type": "string"
            },
            {
              "id": "c7",
              "name": "sent_secret",
              "value": "={{ $json.body.metadata?.webhook_secret || (typeof $json.body.metadata === 'string' ? ($json.body.metadata.match(/\"webhook_secret\"\\s*:\\s*\"([^\"]*)\"/) || [])[1] : '') || '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "config",
      "name": "Chatbot settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        304,
        112
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "sec1",
              "leftValue": "={{ $json.sent_secret }}",
              "rightValue": "={{ $json.webhook_secret }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-secret",
      "name": "Request from your widget?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        544,
        112
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ output: 'Sorry, something went wrong. Please try again later.' }) }}",
        "options": {
          "responseCode": 401
        }
      },
      "id": "reject",
      "name": "Reject unknown requests",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        784,
        272
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.message }}",
        "options": {
          "systemMessage": "=You are the support assistant on {{ $json.company_name }}'s website. Answer from your help content first; raise a ticket only when you genuinely can't resolve something.\n\nWHAT YOU KNOW\n{{ $json.knowledge }}\n\nANSWERING\n- Answer only from the information above. Never invent steps, policies, or timelines.\n- Try to resolve the visitor's issue from your help content before offering to raise a ticket - a ticket is the fallback, not the default.\n\nRAISING A TICKET\n- Only raise a ticket if: (a) the issue genuinely isn't covered by your help content, or (b) the visitor explicitly asks to speak to a person / file a ticket.\n- You need the visitor's email and a clear description of the issue before calling create_ticket. Ask for whichever you're missing.\n- Classify urgency honestly using only these values: low, normal, high, urgent. Most things are normal - reserve high/urgent for things that sound like they're actively blocking the visitor (can't log in, payment failed, service down), not just general dissatisfaction.\n- After the ticket is created, tell the visitor plainly that a human will follow up, and roughly what happens next if the tool response says so.\n\nAlways pass session_id \"{{ $json.session_id }}\" and page_url \"{{ $json.page_url }}\" when calling create_ticket.\n\nTONE\n- Short. Plain. No exclamation marks. Don't over-apologise."
        }
      },
      "id": "agent",
      "name": "Answer questions and raise tickets",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        64,
        640
      ]
    },
    {
      "parameters": {
        "model": "gpt-4.1-mini",
        "options": {}
      },
      "id": "model",
      "name": "GPT-4.1 mini model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        64,
        880
      ]
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Chatbot settings').item.json.session_id }}",
        "contextWindowLength": 20
      },
      "id": "memory",
      "name": "Conversation memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        304,
        880
      ]
    },
    {
      "parameters": {
        "name": "create_ticket",
        "description": "Raise a Zendesk support ticket. Only call this when the issue genuinely can't be resolved from help content, or the visitor explicitly asks for a human. Requires email and a clear description; classify urgency from: low, normal, high, urgent.",
        "workflowId": {
          "__rl": true,
          "mode": "list",
          "value": "PASTE_THE_CREATE_TICKET_WORKFLOW_ID_HERE",
          "cachedResultName": "create_ticket (Zendesk)"
        },
        "specifyInputSchema": true,
        "jsonSchemaExample": "{\n  \"email\": \"sam@example.com\",\n  \"name\": \"Sam\",\n  \"subject\": \"Short summary of the issue\",\n  \"description\": \"Full description of what's wrong\",\n  \"urgency\": \"normal\",\n  \"session_id\": \"\",\n  \"page_url\": \"\"\n}"
      },
      "id": "tool-create-ticket",
      "name": "create_ticket",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 1.2,
      "position": [
        544,
        880
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ output: $json.output }) }}",
        "options": {}
      },
      "id": "respond",
      "name": "Respond to widget",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        544,
        640
      ]
    },
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "sub-trigger",
      "name": "When called by the agent",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        64,
        1388
      ]
    },
    {
      "parameters": {
        "jsCode": "const q = $input.first().json.query ?? $input.first().json;\nconst d = typeof q === 'string' ? JSON.parse(q) : q;\n\nconst email = String(d.email || '').trim().toLowerCase();\nconst valid = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]{2,}$/.test(email);\n\nconst allowedUrgency = new Set(['low', 'normal', 'high', 'urgent']);\nconst urgency = allowedUrgency.has(d.urgency) ? d.urgency : 'normal';\n\nreturn [{ json: {\n  valid,\n  email,\n  name: d.name || '',\n  subject: d.subject || 'New chat support request',\n  description: d.description || '',\n  urgency,\n  session_id: d.session_id || '',\n  page_url: d.page_url || ''\n} }];"
      },
      "id": "parse-input",
      "name": "Parse and validate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        284,
        1388
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "v1",
              "leftValue": "={{ $json.valid }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-valid",
      "name": "Valid email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        504,
        1388
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e1",
              "name": "response",
              "value": "That email address doesn't look valid. Ask them to confirm it, then try again. No ticket has been raised.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "invalid-response",
      "name": "Tell the agent it failed",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        724,
        1548
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "zendesk_subdomain",
              "value": "yourcompany",
              "type": "string"
            },
            {
              "id": "s2",
              "name": "page_url_label",
              "value": "={{ $json.page_url ? '\\n\\nRaised from: ' + $json.page_url : '' }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "sub-config",
      "name": "Tool settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        504,
        1248
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://' + $json.zendesk_subdomain + '.zendesk.com/api/v2/tickets.json' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ ticket: { subject: $json.subject, comment: { body: $json.description + $json.page_url_label }, requester: { name: $json.name || $json.email, email: $json.email }, priority: $json.urgency, tags: ['chatbot', 'urgency_' + $json.urgency, 'session_' + $json.session_id] } }) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "id": "call-zendesk-api",
      "name": "Create Zendesk ticket",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        744,
        1248
      ]
    },
    {
      "parameters": {
        "jsCode": "const full = $input.first().json;\nconst statusCode = full.statusCode || 200;\nconst body = full.body || full;\n\nif (statusCode !== 201 || !body?.ticket?.id) {\n  return [{ json: { response: \"Something went wrong raising the ticket. Let the visitor know a human will need to follow up by email instead, and don't retry silently.\" } }];\n}\n\nreturn [{ json: {\n  response: 'Ticket #' + body.ticket.id + ' raised. A member of the team will follow up by email.'\n} }];"
      },
      "id": "format-response",
      "name": "Format response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        964,
        1248
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Chat webhook": {
      "main": [
        [
          {
            "node": "Chatbot settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chatbot settings": {
      "main": [
        [
          {
            "node": "Request from your widget?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request from your widget?": {
      "main": [
        [
          {
            "node": "Answer questions and raise tickets",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reject unknown requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Answer questions and raise tickets": {
      "main": [
        [
          {
            "node": "Respond to widget",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT-4.1 mini model": {
      "ai_languageModel": [
        [
          {
            "node": "Answer questions and raise tickets",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Conversation memory": {
      "ai_memory": [
        [
          {
            "node": "Answer questions and raise tickets",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "create_ticket": {
      "ai_tool": [
        [
          {
            "node": "Answer questions and raise tickets",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "When called by the agent": {
      "main": [
        [
          {
            "node": "Parse and validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and validate": {
      "main": [
        [
          {
            "node": "Valid email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid email?": {
      "main": [
        [
          {
            "node": "Tool settings",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Tell the agent it failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tool settings": {
      "main": [
        [
          {
            "node": "Create Zendesk ticket",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Zendesk ticket": {
      "main": [
        [
          {
            "node": "Format response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}