Webhooks

Webhooks allow an application to be notified of changes. This is in addition to the ability to fetch those changes as Events, but rather than polling and making outgoing requests repeatedly for changes, webhooks take the form of an incoming call from Asana to your server.

Note that webhooks require keeping a server available to receive requests, and this server has to be available from a public URL. For many scripts this is more complex than making outgoing requests from the script, so consider using events for much of the same value in a lighter-weight format.

NOTE: While Webhooks send arrays of Event objects to their target, the Event objects themselves contain limited information from within Asana and not the complete resource. Changes from Webhooks send even less, ommitting user-created content from their events and only sending data such as gid and resource_type. Here is a sample event from the Events resource:

[
  {
    "resource": {
      "gid": "1337",
      "resource_type": "task",
      "resource_subtype": "default_task",
      "name": "My Task"
    },
    "parent": null,
    "created_at": "2013-08-21T18:20:37.972Z",
    "user": {
      "gid": "1123",
      "resource_type": "user",
      "name": "Tom Bizarro"
    },
    "action": "changed",
    "type": "task"
  }
]

In a Webhook payload you would instead receive this:

[
  {
    "resource": {
      "gid": "1337",
      "resource_type": "task",
      "resource_subtype": "default_task"
    },
    "parent": null,
    "created_at": "2013-08-21T18:20:37.972Z",
    "user": {
      "gid": "1123",
      "resource_type": "user"
    },
    "action": "changed"
  }
]

Webhooks themselves contain only the information necessary to deliver the events to the desired target as they are generated.

IMPORTANT:

During the migration to string IDs in our API there are two possible representations of the events sent by webhooks. The old, deprecated events do not look like the webhook events shown above, where resource and user are JSON objects; rather, the value of these parameters are integer IDs of their respective resources like this:

[
  {
    "resource": 1337,
    "user": 1123,
    "type":"task",
    "action":"changed",
    "created_at": "2013-08-21T18:20:37.972Z",
    "parent":null
  }
]

There is a configuration option for your app in our developer console that allows you to “pin” IDs to either JSON objects with gid (our future state) or to integers (the deprecated state) during the duration of the deprecation. Alternatively, you can create or update existing webhooks with the always_use_string_ids parameter set to true or false to select a format on a per-webhook basis.

Keep in mind that the old-style events are deprecated. after our migration period is over all events will be new-style events and the always_use_string_ids property will be removed.

Webhooks have the following fields:
Field Description
id 1234 Read-only. Globally unique ID of the webhook. Note: This field is under active migration to the gid field—please see our blog post for more information.
gid "1234" Read-only. Globally unique ID of the webhook.
resource_type "webhook" Read-only. The resource type of this resource. The value for this resource is always webhook.
resource { id: 1234, gid: "1234", name: 'Bug task' } Read-only. The resource the webhook is subscribed to.
target 'https://example.com/receive-webhook/7654' Read-only. The URL to receive the HTTP POST.
active false If true, the webhook will send events - if false it is considered inactive and will not generate events.
always_use_string_ids false Webhooks can be created to always send resources with gid parameters if created with this property set to true or to never send them if this property is false (see more above). This is an alternative to changing the configuration of the application in our developer console. This field is temporary and will be removed a short time after our string ID migration is complete.
created_at '2012-02-22T02:06:58.147Z' Read-only. Opt in. The timestamp when the webhook was created.
last_success_at '2012-02-22T02:06:58.147Z' Read-only. Opt in. The timestamp when the webhook last successfully sent an event to the target.
last_failure_at '2012-02-22T02:06:58.147Z' Read-only. Opt in. The timestamp when the webhook last received an error when sending an event to the target.
last_failure_content '500 Server Error\n\nCould not complete the request' Read-only. Opt in. The contents of the last error response sent to the webhook when attempting to deliver events to the target.

CREATE A WEBHOOK

POST    /webhooks

Establishing a webhook is a two-part process. First, a simple HTTP POST similar to any other resource creation. Since you could have multiple webhooks we recommend specifying a unique local id for each target.

Next comes the confirmation handshake. When a webhook is created, we will send a test POST to the target with an X-Hook-Secret header as described in the Resthooks Security documentation. The target must respond with a 200 OK and a matching X-Hook-Secret header to confirm that this webhook subscription is indeed expected.

If you do not acknowledge the webhook’s confirmation handshake it will fail to setup, and you will receive an error in response to your attempt to create it. This means you need to be able to receive and complete the webhook while the POST request is in-flight.

Parameter Description
resource 12345 Required: A resource ID to subscribe to. The resource can be a task or project.
target 'https://example.com/receive-webhook/7654' Required: The URL to receive the HTTP POST.

Example

# Request
curl -H "Authorization: Bearer <personal_access_token>" \
-X POST https://app.asana.com/api/1.0/webhooks \
-d "resource=8675309" \
-d "target=https://example.com/receive-webhook/7654"

# Handshake sent to https://example.com/
POST /receive-webhook/7654
X-Hook-Secret: b537207f20cbfa02357cf448134da559e8bd39d61597dcd5631b8012eae53e81

# Handshake response sent by example.com
HTTP/1.1 200
X-Hook-Secret: b537207f20cbfa02357cf448134da559e8bd39d61597dcd5631b8012eae53e81

# Response
HTTP/1.1 201
{
  "data": {
    "gid": "43214",
    "resource_type": "webhook",
    "resource": {
      "gid": "8675309",
      "resource_type": "project",
      "name": "Bugs"
    },
    "target": "https://example.com/receive-webhook/7654",
    "active": true,
    "created_at": "2018-10-12T19:06:29.993Z",
    "last_success_at": null,
    "last_failure_at": null,
    "last_failure_content": null
  }
}

GET WEBHOOKS

GET    /webhooks

Returns the compact representation of all webhooks your app has registered for the authenticated user in the given workspace.

Parameter Description
workspace "1331" Required: The workspace to query for webhooks in.
resource "12345" Only return webhooks for the given resource.
# Request
curl -H "Authorization: Bearer <personal_access_token>" \
https://app.asana.com/api/1.0/webhooks?workspace=872547

# Response
HTTP/1.1 200
{
  "data": [
    {
      "gid": "43214",
      "resource_type": "webhook",
      "resource": {
        "gid": "1245",
        "resource_type": "project",
        "name": "Bugs"
      },
      "target": "https://example.com/receive-webhook/7654",
      "active": true
    },
    "~..."
  ]
}

GET WEBHOOK

GET    /webhooks/{webhook_gid}

Returns the full record for the given webhook.

Parameter Description
webhook_gid "12345" Required: The webhook to get.
# Request
curl -H "Authorization: Bearer <personal_access_token>" \
https://app.asana.com/api/1.0/webhooks/43214

# Response
HTTP/1.1 200
{
  "data": {
    "gid": "43214",
    "resource_type": "webhook",
    "resource": {
      "gid": "1245",
      "resource_type": "project",
      "name": "Bugs"
    },
    "target": "https://example.com/receive-webhook/7654",
    "active": true
  }
}

RECEIVING WEBHOOK EVENTS Because multiple events often happen in short succession, a webhook payload is designed to be able to transmit multiple events at once. The exact model of events is described in the Events documentation.

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 using the shared secret (transmitted during the handshake) of the request body. Verification is strongly recommended, as it would otherwise be possible for an attacker to POST a malicious payload to the same endpoint. If the target endpoint can be kept secret this risk is mitigated somewhat, of course.
  • A JSON body with a single key, events, containing an array of the events that have occurred since the last webhook delivery. Note that this list may be empty, as periodically we may send a “heartbeat” webhook to verify that the endpoint is available.

Note that events are “skinny” - we expect consumers who desire syncing data to make additional calls to the API to retrieve the latest state. Because the data may have already changed by the time we send the event, it would be misleading to send a snapshot of the data along with the event.

Example

# Request to your server
POST /receive-webhook/7654
X-Hook-Signature: 1d6207f8818f063890758a32d3833914754ba788cb8993e644701bac7257f59e

{
  "events": [
    {
      "action": "changed",
      "created_at": "2013-08-21T18:20:37.972Z",
      "parent": null,
      "resource": {
        "gid": "3579",
        "resource_type": "task",
        "resource_subtype": "default_task"
      },
      "user": {
        "gid": "1123",
        "resource_type": "user"
      }
    },
    {
      "action": "changed",
      "created_at": "2013-08-21T18:22:45.421Z",
      "parent": null,
      "resource": {
        "gid": "1338",
        "resource_type": "task",
        "resource_subtype": "default_task"
      },
      "user": {
        "gid": "1423",
        "resource_type": "user"
      }
    },
  ]
}

ERROR HANDLING AND RETRY 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 deactivated.

DELETE A WEBHOOK

DELETE    /webhooks/{webhook_gid}

This method permanently removes a webhook. Note that it may be possible to receive a request that was already in flight after deleting the webhook, but no further requests will be issued.

Parameter Description
webhook_gid 12345 Required: The webhook to delete.
# Request
curl --request DELETE -H "Authorization: Bearer <personal_access_token>" \
https://app.asana.com/api/1.0/webhooks/43214

# Response
HTTP/1.1 200
{
  "data": {}
}