ITADN

Jira webhook routing: wrong payload paths + fundamentally broken connection matching

#5682Opensteffendsommer 创建于 2026-03-20
Stale
S
steffendsommercommented
## Summary Jira webhook routing is broken in two ways: 1. **Wrong lodash paths** — uses `payload.webhookEvent` and `payload.user.accountId` but Jira sends these at the root level (`webhookEvent`, `user.accountId`) 2. **Wrong connection matching strategy** — matches on `user.accountId` (the individual Jira user who performed the action), but this is fundamentally incorrect. Every other Nango integration matches on a workspace/org-level identifier. This supersedes #5400, which correctly identified problem (1) but not (2). PR #5408 (the pending fix for #5400) only removes the `payload.` prefix, which would fix issue events but still fail for comment, sprint, and board events. ## Root Cause ### Problem 1: Incorrect paths The handler passes `payload.webhookEvent` and `payload.user.accountId` to `executeScriptForWebhooks`, but Jira payloads have these at the root level. `lodash.get(body, 'payload.user.accountId')` returns `undefined`, so connection matching always fails. ### Problem 2: Wrong identifier strategy Even with corrected paths, matching on `user.accountId` is wrong because: - **It only matches if the authing user triggered the event.** If any other user in the Jira workspace creates an issue or adds a comment, it won't match any connection. - **`user` is not present on all event types.** Comment events put the user at `comment.author.accountId`. Sprint and board events have **no `user` field at all**. - **Every other Nango integration matches on a workspace-level identifier:** | Provider | Matches on | |---|---| | GitHub App | `installation_id` | | HubSpot | `portalId` | | Linear | `organizationId` | | Slack | `team.id` | | Shopify | `subdomain` | | **Jira (current)** | `accountId` (individual user) | ## Evidence Real `comment_created` webhook payload from Nango logs — note `webhookEvent` is at root, and there is **no root-level `user` field**: ```json { "webhookEvent": "comment_created", "timestamp": 1773931953555, "matchedWebhookIds": [208], "issue": { "self": "https://example.atlassian.net/rest/api/2/issue/42766", ... }, "comment": { "author": { "accountId": "712020:a77f5b24-...", ... }, "self": "https://example.atlassian.net/rest/api/2/issue/42766/comment/61142", ... } } ``` The response was **400**, confirming routing failure. ## Suggested Fix: Match on `baseUrl` via `self` URL Every Jira webhook payload contains resource objects (`issue`, `comment`, `sprint`, `board`, etc.) with a `self` URL that includes the Jira site domain (e.g., `https://example.atlassian.net/rest/api/2/...`). The Jira post-connection script already stores `baseUrl` in `connection_config`. The fix is to extract the site origin from any `self` URL in the payload and match it against `baseUrl`: ```typescript const route: WebhookHandler = async (nango, _headers, body) => { // Extract base URL from any self link in the payload const selfUrl = body?.issue?.self || body?.comment?.self || body?.sprint?.self || body?.board?.self || body?.worklog?.self || body?.version?.self; const baseUrl = selfUrl ? new URL(selfUrl).origin : undefined; const response = await nango.executeScriptForWebhooks({ body, webhookType: 'webhookEvent', connectionIdentifierValue: baseUrl, propName: 'baseUrl' }); return Ok({ content: { status: 'success' }, statusCode: 200, connectionIds: response?.connectionIds || [], toForward: body }); }; ``` ### Why this approach - **Works for all event types** — `self` URLs are present on every Jira resource object ([Atlassian docs](https://developer.atlassian.com/cloud/jira/platform/webhooks/): "webhook payloads are the same shape returned by the Jira REST API") - **Matches at workspace level** — consistent with how every other integration works - **No external dependencies** — doesn't require API-based webhook registration or 30-day refresh cycles - **Uses existing data** — `baseUrl` is already stored by the [Jira post-connection script](https://github.com/NangoHQ/nango/blob/master/packages/server/lib/hooks/connection/providers/jira/post-connection.ts) ### Verified `self` presence by event type | Event type | `self` location | Evidence | |---|---|---| | `jira:issue_created/updated` | `issue.self` | Official docs | | `comment_created/updated` | `comment.self` | Server docs + REST API | | `sprint_created/started/closed` | `sprint.self` | Captured payloads | | `board_created/updated` | `board.self` | REST API schema (no webhook example exists) | | `worklog_created/updated` | `worklog.self` | REST API | | `version_created/updated` | `version.self` | REST API | ### Array handling The current handler also supports array payloads. The same approach applies — extract `self` from each event in the array. ## References - #5400 — original issue (superseded by this one) - #5408 — pending PR with incomplete fix - [Jira Cloud Webhooks docs](https://developer.atlassian.com/cloud/jira/platform/webhooks/) - [Jira Software Webhooks docs](https://developer.atlassian.com/cloud/jira/software/webhooks/)
3 条评论