Connect Asana with Jira

Implementation Guide

Overview: Connecting Asana and Jira

Engineering and product teams operating at scale frequently face a tool fragmentation problem: project managers and non-technical stakeholders prefer Asana for its intuitive task management interface, while development teams live in Jira for its deep integration with Git workflows, sprint planning, and bug tracking. When these two systems operate without synchronization, work items must be manually duplicated across platforms, status updates fall out of sync, and cross-functional visibility breaks down. The Asana-to-Jira integration solves this by establishing a programmatic bridge between the two platforms, ensuring that tasks created or updated in Asana are reflected as issues in Jira and vice versa.

This guide focuses on implementing a robust, production-grade sync using Make (formerly Integromat) as the orchestration layer, with detailed coverage of the Asana REST API webhook configuration, the Jira REST API v3 issue creation endpoint, and the bidirectional sync architecture required to prevent event loops.

Core Prerequisites

For Asana, you need an Organization Admin or Workspace Admin account to configure webhooks. You must generate a Personal Access Token (PAT) from Asana's developer settings at app.asana.com/0/my-apps. Alternatively, for multi-user deployments, register an OAuth 2.0 application in the Asana Developer Console and obtain a client ID and client secret. The required OAuth scopes are default (which covers tasks, projects, workspaces, and users) and openid if you need identity assertions. Asana's webhook system requires that your receiving endpoint be publicly accessible over HTTPS and respond to handshake requests (described below).

For Jira, you need a Jira Software account with the "Jira Administrators" global permission to create API tokens and configure project-level permissions. Generate an API token from your Atlassian account settings at id.atlassian.com/manage-profile/security/api-tokens. The API token is used in combination with your Atlassian account email address for Basic Authentication against the Jira REST API v3. The base URL for your Jira instance will be https://[your-domain].atlassian.net/rest/api/3/. Ensure that the Jira project you are writing to has a permission scheme that grants "Create Issues" and "Edit Issues" to the authenticated user.

If you are using Jira Server or Jira Data Center rather than Jira Cloud, the authentication mechanism differs: you use HTTP Basic Auth with a username and password or a dedicated service account, and the API base URL will be your self-hosted domain. The REST API v3 endpoints are only available on Jira Cloud; Jira Server uses REST API v2.

Top Enterprise Use Cases

The primary use case is cross-team work item synchronization. When a product manager creates a task in Asana titled "Implement OAuth2 login flow" and assigns it to an engineering project, the integration automatically creates a corresponding Jira Story in the appropriate sprint board. The Jira issue is populated with the task's description, due date, and assignee (mapped via email address lookup), and the Asana task's custom field for "Jira Issue Key" is back-populated with the created issue's key (e.g., ENG-4821).

A second use case is status synchronization. When a developer transitions a Jira issue to "Done," the corresponding Asana task should be marked as complete. This requires a bidirectional webhook architecture and careful deduplication logic to prevent the completion event from triggering a new Asana event that then writes back to Jira in an infinite loop.

A third enterprise use case is escalation routing. When an Asana task is flagged with a specific custom field value (e.g., "Severity: Critical"), the integration creates a Jira bug with priority set to "Highest" and triggers a Jira automation rule to notify the on-call engineering lead via PagerDuty.

Step-by-Step Implementation Guide

Configuring Asana Webhooks

Asana uses a push-based webhook model. To register a webhook, you make a POST request to the Asana Webhooks API endpoint. The webhook must target a specific Asana resource—either a project (to receive all task events within that project) or a workspace. Use the following curl command to register the webhook, replacing the values with your own:

curl -X POST https://app.asana.com/api/1.0/webhooks \
  -H "Authorization: Bearer YOUR_ASANA_PAT" \
  -H "Content-Type: application/json" \
  -d {
    "data": {
      "resource": "PROJECT_GID",
      "target": "https://hook.eu1.make.com/YOUR_MAKE_WEBHOOK_ID",
      "filters": [
        {
          "resource_type": "task",
          "action": "added"
        },
        {
          "resource_type": "task",
          "action": "changed",
          "fields": ["completed", "assignee", "due_on", "name"]
        }
      ]
    }
  }

Upon registration, Asana will immediately send a handshake request to your target URL with a header X-Hook-Secret containing a shared secret. Your endpoint must respond with a 200 OK and echo the same X-Hook-Secret value in the response headers. Make's Custom Webhook module handles this automatically. Store this secret value securely; Asana will include an X-Hook-Signature header on all subsequent event deliveries, which is an HMAC-SHA256 signature of the request body using the hook secret. You should validate this signature on every incoming request to prevent spoofed payloads.

A sample Asana task-added webhook payload looks like this:

{
  "events": [
    {
      "user": {
        "gid": "12345678",
        "resource_type": "user"
      },
      "created_at": "2024-03-15T10:23:45.123Z",
      "action": "added",
      "resource": {
        "gid": "9876543210",
        "resource_type": "task",
        "name": "Implement OAuth2 login flow"
      },
      "parent": {
        "gid": "PROJECT_GID",
        "resource_type": "project"
      }
    }
  ]
}

Note that Asana webhook payloads are intentionally sparse—they contain the event type and resource GID but not the full task data. You must make a follow-up GET request to https://app.asana.com/api/1.0/tasks/TASK_GID?opt_fields=name,notes,due_on,assignee.email,custom_fields,completed to retrieve the full task object. In Make, this is implemented by adding an HTTP module after the webhook trigger to fetch the full task data before proceeding to the Jira action.

Creating a Jira Issue via REST API v3

Once you have the full Asana task data, construct the Jira issue creation payload. The Jira REST API v3 endpoint for issue creation is POST https://[your-domain].atlassian.net/rest/api/3/issue. Authentication uses HTTP Basic Auth with your email and API token encoded in Base64, passed in the Authorization: Basic [base64(email:token)] header.

A representative Jira issue creation payload for a Story type is:

{
  "fields": {
    "project": {
      "key": "ENG"
    },
    "summary": "Implement OAuth2 login flow",
    "description": {
      "type": "doc",
      "version": 1,
      "content": [
        {
          "type": "paragraph",
          "content": [
            {
              "type": "text",
              "text": "Synced from Asana task GID 9876543210. Original description: [task notes content here]"
            }
          ]
        }
      ]
    },
    "issuetype": {
      "name": "Story"
    },
    "priority": {
      "name": "Medium"
    },
    "duedate": "2024-04-01",
    "assignee": {
      "accountId": "JIRA_ACCOUNT_ID"
    },
    "labels": ["asana-sync"],
    "customfield_10014": "SPRINT_ID"
  }
}

Note that Jira Cloud's description field uses the Atlassian Document Format (ADF), not plain text or Markdown. You must wrap all text content in the ADF document structure shown above. If your Asana task notes contain Markdown, you will need a transformation step in Make using the Text Parser module to convert the Markdown to ADF-compatible plain text, or use Atlassian's ADF builder library if you are implementing a custom Node.js function.

To map Asana assignees to Jira assignees, you need to resolve the Asana user's email address to a Jira accountId. Make a GET request to the Jira User Search API: GET https://[domain].atlassian.net/rest/api/3/user/search?query=EMAIL_ADDRESS. Extract the accountId from the response and use it in the assignee field of the issue payload.

After creating the Jira issue, use the Jira issue key from the creation response (e.g., ENG-4821) to update the originating Asana task with a custom field that stores the Jira key. This back-population step is essential for the deduplication logic in the bidirectional sync.

Preventing Sync Loops

To prevent an event in Jira from triggering an Asana update that then triggers another Jira update, implement a "sync lock" pattern. In Make, use a Data Store to maintain a set of recently processed resource IDs with timestamps. At the start of each scenario run, check whether the incoming Asana task GID or Jira issue key is present in the Data Store with a timestamp within the last 60 seconds. If it is, skip processing and return early. If it is not, write the ID and current timestamp to the Data Store, then proceed with the sync logic. This pattern reliably eliminates infinite loops without requiring a dedicated database.

Common Pitfalls & Troubleshooting

A 400 Bad Request from the Jira issue creation endpoint almost always indicates a malformed ADF description payload or a missing required field for the target project's issue type scheme. Enable Jira's API response body logging in Make to see the exact validation error message returned by Jira. Common sub-errors include "Field 'assignee' cannot be set' when the target user does not have project access, and "The issue type is not valid for this project" when the issue type name does not exactly match a configured type in the project's issue type scheme.

A 403 Forbidden from the Jira API typically means the authenticated user's API token lacks project-level permissions. Verify that the Jira user associated with the API token has been granted the "Create Issues" project permission in the project permission scheme. This is configured in Jira under Project Settings > Permissions.

Asana webhook delivery failures result in a 410 Gone response if the target URL is unreachable for an extended period. Asana will automatically deactivate webhooks that consistently fail delivery and return a 400 or 5xx status. To monitor webhook health, use the Asana API to list active webhooks: GET https://app.asana.com/api/1.0/webhooks?workspace=WORKSPACE_GID. If a webhook's active field is false, you must re-register it.

Rate limits on the Asana API are enforced at 1,500 requests per minute per user token. If your integration processes a high volume of task events simultaneously (e.g., during a bulk import), implement exponential backoff on HTTP 429 responses. The Asana API returns a Retry-After header indicating the number of seconds to wait before retrying. The Jira Cloud REST API imposes rate limits at the tenant level, and sustained high-volume integrations should use the Jira Bulk operations endpoint where available.