Jira webhook routing: wrong payload paths + fundamentally broken connection matching
Stale
## 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 条评论