# n8n ↔ Zendesk Web Widget (Classic) – JWT Integration

<div class="paragraph" id="bkmrk-internal-documentati">*Internal Documentation v1.0 – 2025-08-25*</div>> <div class="paragraph">**Goal**  
> Allow visitors authenticated through your n8n chat front-end to start a Zendesk Web Widget session, while still requiring a human agent to approve any ticket creation.</div>

## 1. Prerequisites

<table data-v-0909cf3c="" id="bkmrk-item-where-to-find-z"><thead data-v-0909cf3c=""><tr data-v-0909cf3c=""><th align="left" data-v-0909cf3c="">Item</th><th align="left" class="" data-v-0909cf3c="">Where to find</th></tr></thead><tbody data-v-0909cf3c=""><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">Zendesk account with **Web Widget (Classic)** enabled</td><td align="left" class="" data-v-0909cf3c="">Admin Center → Channels → Widget</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**Shared Secret** for JWT</td><td align="left" class="" data-v-0909cf3c="">Admin Center → Channels → Chat → Widget → *Authentication*</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">n8n instance reachable from the public internet</td><td align="left" class="" data-v-0909cf3c="">`https://your-n8n.com`</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">Existing n8n workflow that pauses for human review</td><td align="left" class="" data-v-0909cf3c="">(Human-in-the-Loop)</td></tr></tbody></table>

## 2. High-Level Flow

1. <div class="paragraph">Visitor loads your web-chat.</div>
2. <div class="paragraph">Front-end requests a **JWT** from n8n.</div>
3. <div class="paragraph">n8n signs and returns the JWT.</div>
4. <div class="paragraph">Zendesk Web Widget starts an **authenticated chat** session.</div>
5. <div class="paragraph">**Human-in-the-Loop** still controls *ticket creation* (Wait node).</div>

---

## 3. n8n Endpoints

### 3.1 JWT Issuer (`POST /webhook/zendesk-jwt`)

<div class="paragraph" id="bkmrk-purpose%3A-zendesk-wil">**Purpose:** Zendesk will call this endpoint to verify the visitor.</div>#### Workflow Steps

<div class="table markdown-table" data-v-0909cf3c="" data-v-20851492="" id="bkmrk-node-settings-webhoo"><div class="table-container" data-v-0909cf3c=""><table data-v-0909cf3c=""><thead data-v-0909cf3c=""><tr data-v-0909cf3c=""><th align="left" data-v-0909cf3c="">Node</th><th align="left" class="" data-v-0909cf3c="">Settings</th></tr></thead><tbody data-v-0909cf3c=""><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**Webhook**</td><td align="left" class="" data-v-0909cf3c="">Path = `/webhook/zendesk-jwt` (POST)  
  
</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**Lookup User**</td><td align="left" class="" data-v-0909cf3c="">Any node that confirms the `user_token` sent by Zendesk is valid (Database, Google Sheets, etc.).</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**JWT Sign**</td><td align="left" class="" data-v-0909cf3c="">Algorithm = `HS256`  
  
  
  
  
  
</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**Respond to Webhook**</td><td align="left" class="" data-v-0909cf3c="">Status = `200`  
  
</td></tr></tbody></table>

</div></div>#### Example cURL

```bash
curl -X POST \
  'https://your-n8n.com/webhook/zendesk-jwt?user_token=abc123' \
  -H 'Content-Type: application/json'
```

<div data-v-84e40a1f="" id="bkmrk--1">  
</div><div class="paragraph" id="bkmrk-expect%3A">Expect:</div>```json
{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
```

### 3.2 Optional Token Generator for Front-End (`GET /webhook/chat-token`)

<div class="paragraph" id="bkmrk-if-your-front-end-ne">If your front-end needs to fetch the token itself (instead of letting Zendesk call the endpoint directly), create a second simple workflow:</div><div class="table markdown-table" data-v-0909cf3c="" data-v-20851492="" id="bkmrk-node-settings-webhoo-1"><div class="table-container" data-v-0909cf3c=""><table data-v-0909cf3c=""><thead data-v-0909cf3c=""><tr data-v-0909cf3c=""><th align="left" data-v-0909cf3c="">Node</th><th align="left" class="" data-v-0909cf3c="">Settings</th></tr></thead><tbody data-v-0909cf3c=""><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">Webhook</td><td align="left" class="" data-v-0909cf3c="">Method = `GET`</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">JWT Sign</td><td align="left" class="" data-v-0909cf3c="">Same payload &amp; secret as above</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">Respond to Webhook</td><td align="left" class="" data-v-0909cf3c="">Body = `{"jwt":"{{ $('JWT Sign').item.jwt }}"}`</td></tr></tbody></table>

</div></div>---

## 4. Zendesk Configuration

<div class="paragraph" id="bkmrk-admin-center-%E2%86%92-chann">Admin Center → Channels → **Chat** → **Widget** → **Authentication**</div><div class="table markdown-table" data-v-0909cf3c="" data-v-20851492="" id="bkmrk-field-value-authenti"><div class="table-container" data-v-0909cf3c=""><table data-v-0909cf3c=""><thead data-v-0909cf3c=""><tr data-v-0909cf3c=""><th align="left" data-v-0909cf3c="">Field</th><th align="left" class="" data-v-0909cf3c="">Value</th></tr></thead><tbody data-v-0909cf3c=""><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**Authentication Method**</td><td align="left" class="" data-v-0909cf3c="">JWT</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**JWT URL**</td><td align="left" class="" data-v-0909cf3c="">`https://your-n8n.com/webhook/zendesk-jwt`</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">**JWT Secret**</td><td align="left" class="" data-v-0909cf3c="">*Paste the same Shared Secret used in n8n*</td></tr></tbody></table>

</div></div>## 5. Front-End Snippet

<div class="paragraph" id="bkmrk-add-this-after-the-z">Add this after the Zendesk Web Widget script is loaded:</div>```html
<script>
  // Replace with your n8n endpoint if you created /webhook/chat-token
  fetch('/api/n8n/get-chat-token', { credentials: 'include' })
    .then(r => r.json())
    .then(({ jwt }) => {
      zE('webWidget', 'chat:setJwtFn', callback => callback(jwt));
    });
</script>
```

> <div class="paragraph">If you let Zendesk call your endpoint directly, omit the fetch and simply use the JWT URL configured above.</div>

## 6. Human-in-the-Loop Remains Intact

<div class="paragraph" id="bkmrk-your-existing-workfl">Your existing workflow already has a **Wait** node that pauses until an agent approves.  
Nothing in the JWT flow changes that—ticket creation only proceeds **after** the webhook resume call.</div>## 7. Security Checklist

- <div class="paragraph">[ ] HTTPS only (n8n &amp; Zendesk endpoints).</div>
- <div class="paragraph">[ ] Rotate Shared Secret periodically → update both Zendesk and n8n JWT node.</div>
- <div class="paragraph">[ ] Log every JWT issuance (`iat`, IP, user_token).</div>
- <div class="paragraph">[ ] Validate `user_token` strictly; deny unknown tokens immediately.</div>

## 8. Troubleshooting Quick-Table

<div class="table markdown-table" data-v-0909cf3c="" data-v-20851492="" id="bkmrk-symptom-likely-cause"><div class="table-container" data-v-0909cf3c=""><table data-v-0909cf3c=""><thead data-v-0909cf3c=""><tr data-v-0909cf3c=""><th align="left" data-v-0909cf3c="">Symptom</th><th align="left" data-v-0909cf3c="">Likely Cause</th><th align="left" class="" data-v-0909cf3c="">Fix</th></tr></thead><tbody data-v-0909cf3c=""><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">Widget shows “Unable to authenticate”</td><td align="left" class="" data-v-0909cf3c="">Wrong Shared Secret or expired `iat`</td><td align="left" class="" data-v-0909cf3c="">Check secret &amp; timestamp</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">404 from Zendesk</td><td align="left" class="" data-v-0909cf3c="">Wrong JWT URL</td><td align="left" class="" data-v-0909cf3c="">Ensure `https://your-n8n.com/webhook/zendesk-jwt` is publicly reachable</td></tr><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">“Invalid JWT format”</td><td align="left" class="" data-v-0909cf3c="">Payload missing required claims</td><td align="left" class="" data-v-0909cf3c="">Ensure `name`, `email`, `jti`, `iat` are present</td></tr></tbody></table>

</div></div>## 9. Change Log

<div class="table markdown-table" data-v-0909cf3c="" data-v-20851492="" id="bkmrk-date-author-notes-20"><div class="table-container" data-v-0909cf3c=""><table data-v-0909cf3c=""><thead data-v-0909cf3c=""><tr data-v-0909cf3c=""><th align="left" data-v-0909cf3c="">Date</th><th align="left" data-v-0909cf3c="">Author</th><th align="left" class="" data-v-0909cf3c="">Notes</th></tr></thead><tbody data-v-0909cf3c=""><tr data-v-0909cf3c=""><td align="left" class="" data-v-0909cf3c="">2025-08-25</td><td align="left" class="" data-v-0909cf3c="">DevOps</td><td align="left" class="" data-v-0909cf3c="">Initial draft based on Zendesk article [4408838925082](https://support.zendesk.com/hc/en-us/articles/4408838925082)</td></tr></tbody></table>

</div></div><div class="paragraph" id="bkmrk--3"></div>