Skip to content

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

@steffendsommer

Description

@steffendsommer

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:

{
  "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:

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 typesself URLs are present on every Jira resource object (Atlassian docs: "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 databaseUrl is already stored by the Jira post-connection script

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions