Overview

Webhooks allow an application to be notified of changes in Asana.

This is similar to our events resource, but webhooks "push" events via HTTP POST rather than expecting integrations to repeatedly "poll" for them. For services that are already accessible on the internet, this is often more convenient and efficient.

However, webhooks require a server to be accessible over the internet at all times to receive the event. For most simple integrations, events provide much of the same benefits while using a significantly simpler implementation which does not require maintaining an internet-accessible server.

Webhooks and events streams are served from the same infrastructure, where, on average, events are delivered within a minute of occurring, and most should be delivered within 10 minutes of occurring. Review Timing for more information.

This system is designed for at-most-once delivery, meaning in exceptional circumstances we may fail to deliver events. Furthermore, webhooks cannot be replayed once delivered. For these reasons, if your use case requires strong guarantees about processing all changes on a resource and cannot tolerate any missing events, regardless of how rare that might be, we recommend building a fallback polling system that fetches the resource periodically as well. Note that, if your server does not respond to a webhook with a successful HTTP status code within 10 seconds, Asana will try to resend the webhook for up to 24 hours before giving up.


Setting up a webhook

In order to work with Asana webhooks, you must create and host a server that can receive and handle webhook events. This server must expose an HTTP endpoint (i.e., a publicly-accessible URL) to function as the reception and processing point for all incoming events. During the development and testing process, if you set up such a server on your local machine (i.e., a localhost service), you may choose to use a "tunneling" tool such as ngrok to expose that local server to the internet.

📘

Example webhook servers

The following are example webhook servers that demonstrates the features of Asana webhooks. Each example server includes how to handle the webhook handshake as well as how to receive and verify webhook events.

To try out the demo out for yourself, be sure to generate a new personal access token, then follow the instructions in the README.

The following video tutorial walks you through the process of:

  1. Setting up an example webhook server [1:58]
  2. Setting up business logic for the webhook handshake [3:45]
  3. Establishing a webhook on an Asana resource [6:45]
  4. Verifying and receiving webhook events[9:02]

You can find the tutorial source code here.

The webhook handshake

Before you can begin receiving webhook events about an Asana resource, you will need to establish a webhook connection and complete the initial handshake. To ensure that the receiving server is available to receive incoming events from a webhook, Asana will POST to the requested target endpoint during the webhook creation request. In other words, the outgoing webhook creation request will wait to return until another full POST request from Asana's servers to the target has been completed, then the webhook creation request can return with a successful response.

Note: This means that your server must be able to handle being blocked on the outgoing create request while still being able to receive and handle an incoming request. A common reason that webhook handshakes fail is that servers are not able to asynchronously handle the handshake request.

Included in the webhook handshake is a HTTP header called X-Hook-Secret. To successfully complete the handshake, the receiving server should echo back the same header with the same value and a 200 OK or 204 No Content response code.

The purpose of this header is to provide a shared secret that both Asana and the receiving server both store. This is the only time it will be transmitted. In future webhook events, Asana will use this key to compute a signature over the webhook callback request's body which can be used to verify that the incoming request was genuine (details below). We strongly recommend that you take advantage of this security feature and reject webhooks that have an invalid signature.

For an example of how this might be implemented in code, see our sample implementation in Node.js and Python

Filtering

Webhook events will "propagate up" from contained objects through to parent objects. For example, changes to comments will be sent to webhooks on the parent task and to ones on the task's projects. This way, a webhook on a project will be notified of all changes that occur in all of its tasks, subtasks of those tasks, and comments on those tasks and subtasks.

This can be a lot of data, some of which might not be relevant to a particular integration, so Asana's webhooks have a filtering feature which allows integrations to specify only the types of changes that they care about. By specifying the list of webhook filters on webhook creation, an integration can select just the subset of events it wants to receive. When filters are specified on the webhook, events will only be delivered if they pass any of the filters specified when creating the webhook.

To reduce the volume of data to transfer, webhooks created on team, portfolio, or workspace must specify filters. In addition, the set of event filters that can be placed on a team-level or workspace-level webhook is more limited than filters for webhooks that are created on lower-level resources:

  • Webhook events from tasks, subtasks, and stories won't be propagated to these higher-level webhooks, so all changes on these resources are automatically filtered out.
  • Webhook events from project resources can be filtered for these actions: added, removed, deleted, undeleted, and changed.
  • Webhook events from team_membership resources can be filtered to actions added and removed.
  • Webhook events from workspace_membership resources can be filtered to added and removed.
  • Webhook events from portfolio resources can be filtered to added.

Actions

Actions define the type of action that was taken on the resource to trigger an event for your webhook. When you receive a webhook event, there will be an associated action in the event response that indicates the action that triggered the event. Additionally, actions are used in webhook filters. You can specify an action and a resource_type in the filters parameter when establishing a webhook so that you will only receive events matching the action specified for the resource in your filter.

The following is a list of actions that we support:

  • added - a new resource was created
  • changed - the resource was modified
  • deleted - the resource itself was deleted
  • removed - the resource was removed from a parent
  • undeleted - the deletion of the resource was undone

Resources and actions

Below is a list of resources and actions that can trigger an event for those resources. This is not an exhaustive list, but should cover the most common use cases.

  • Attachment - deleted, undeleted
  • Goal - added, changed, removed, deleted, undeleted
  • Portfolio - added, deleted, removed
  • Project - added, changed, deleted, removed, undeleted
  • Project Membership - added, removed
  • Project Template Configuration - added, deleted, removed
  • Project Template Configuration Membership - added, removed
  • Section - added, changed, deleted, undeleted
  • Story: type: <all other types> added, removed, undeleted
  • Story: type: comment added, changed, removed, undeleted
  • Tag - added, changed, deleted, undeleted
  • Task - added, changed, deleted, removed, undeleted
  • Team - added, changed, deleted
  • Team Membership - added, removed
  • Time Tracking Entry - added, changed, deleted
  • Workspace - changed
  • Workspace Memberships - added, removed

For example, let's say you establish a webhook for an attachment by providing the GID of an attachment in your resource parameter. This means that based on the resource and action definition for attachment above, a deleted or an undeleted action will trigger your attachment webhook.


Events

Receiving events

Because multiple events often happen in short succession, a webhook payload is designed to be able to transmit multiple events at once. The schema of these events is described in event.

The HTTP POST that the target receives contains:

  • An X-Hook-Signature header, which allows verifying that the payload is genuine. The signature is a SHA256 HMAC signature computed on the request body using the shared secret transmitted during the handshake. Verification is strongly recommended, as it would otherwise be possible for an attacker to POST malicious payload to the same endpoint.
  • A JSON body with a single key, events, containing an array of the events that have occurred since the last webhook delivery. Note: this list may be empty, as periodically we send a "heartbeat" webhook to verify that the endpoint is still available.

Note that events are "compact" and contain only some basic details of the change, not the whole resource. We expect integrations to make additional requests to the API to retrieve the latest state from Asana.

Timing

Events are delivered in under a minute on average. Most events are delivered within 10 minutes. In exceptional circumstances, an event may arrive after more than 10 min. If your service relies on real-time processing of Asana webhooks, we recommend that you examine the created_at field on events before processing to ensure the event is still relevant for your needs.

Errors and retries

If we attempt to send a webhook payload and we receive an error status code, or the request times out, we will retry delivery with exponential backoff. In general, if your servers are not available for an hour, you can expect it to take no longer than approximately an hour after they come back before the paused delivery resumes. However, if we are unable to deliver a message for 24 hours, the webhook will be deleted.

Heartbeat events

Webhooks keep track of the last time that delivery succeeded, and this time is updated with each success (i.e, last_success_at). A delivery succeeds when your webhook server responds to a webhook event with a 200 OK or 204 No Content response code. To help facilitate this delivery succeeded tracking, webhooks have a “heartbeat” that will deliver an empty payload to your webhook server at the initial handshake, and then at every eight hours. This way, even if there is no activity on the resource, the last success time (i.e last_success_at) will still be updated continuously.

Note that if we do not receive a response to a "heartbeat" after 24 hours we will delete that webhook connection. This means that specific webhook route will not receive future events from Asana. If you make a request for that specific webhook (GET /webhooks/{webhook_gid}), that webhook will no longer be available.

Additionally, when a webhook's resource is deleted, Asana will automatically delete the webhook in 72 hours.

Scenario 1: successful heartbeat (after initial handshake)

Scenario 2: failed heartbeat (after initial handshake)


Security

In order to receive webhook events, your webhook endpoint needs to be accessible over the internet. As such, because your webhook endpoint is publicly accessible over the internet, it is vulnerable to receiving events that are not from Asana. To ensure that your webhook endpoint receives events from Asana, it is important to verify each request.

Request verification can be done by comparing an HMAC signature generated from the X-Hook-Secret and request body to the X-Hook-Signature. When you first establish a webhook with Asana, we send your webhook endpoint an X-Hook-Secret. Once received, this X-Hook-Secret should be stored by your webhook server.

When a webhook event is triggered, Asana sends along a X-Hook-Signature in the request header. At this point, your webhook server should extract the body from the request and use it along with the stored X-Hook-Secret to generate an HMAC signature. HMAC signatures can be generated using a HMAC library in your language of development and passing in the X-Hook-Secret as the secret and in the request body as the message. The generated HMAC signature should match the X-Hook-Signature sent in the request header. If these signatures differ, it can indicate that the event received was not from Asana.

To read more about our X-Hook-Signature see receiving events. For examples of servers configured to verify HMAC signatures, see our example webhook servers.


Limits

Webhooks have two different limits:

  • 1,000 webhooks per resource. For example, if 10 apps/users each have 100 webhooks watching the same resource, no additional webhooks can be established on that particular resource. Note that /events streams also count toward this limit
  • 10,000 webhooks per token. That is, a single token can have 10,000 active webhooks at any one time

📘

Managing webhooks limits

If you no longer require a webhook to be active, please send a HTTP 410 response when you receive a webhook event or webhook heartbeat event. This lets us know that you no longer want to receive webhook events for this particular webhook and we will delete this webhook.

Alternatively, you may send a (DELETE /webhooks/{webhook_gid}) request at anytime.


Troubleshooting

Webhook stopped receiving events

This can happen when your registered webhook endpoint ignores incoming heartbeat events. We send periodic heartbeat events to your webhook endpoint every 8 hours to keep track of the last time that delivery succeeded. If we receive no response to heartbeat events after 24 hours, we will delete the registered webhook connection.

To fix this, you will need to modify your webhook endpoint code to respond to heartbeat events and re-establish a webhook connection.

Computed webhook signature differs from X-Hook-Signature

When computing your SHA256 HMAC signature, make sure to utilize the X-Hook-Secret and the full body of the request. The X-Hook-Secret can be found in the header of the initial request sent to your webhook endpoint during the initial handshake process. The full body of the request should also be used, not just a portion of the data inside the request body.

Additionally, check the documentation of the library you are using to compute the SHA256 HMAC signature, as it may require you to convert the request body from an object into a string.