Asana encodes encoded webhook target URL before POSTing events

Hey there,

we’re building a platform that dynamically registers webhooks on behalf of users. Therefore we have a mapping from webhook URL to user, so that we know which account a given event is targeting.

in order to do the mapping, we create paths that may contain special characters like a pipe |.

To provide an example, say a route for a given account contains a some|test element. We’ll register a webhook with a target URL webhooks/json/asana/some%7Ctest/task/123456, i.e. we url-encode each path segment. In other words, the targetURL we register for the webhook is a valid, URL-safe string.

When retrieving the registered webhook (a GET against /api/1.0/webhooks/:id), Asana lists the webhook config and target URL correctly with the above path segments.

However, when POSTing events to our server, Asana posts to webhooks/json/asana/some%257Ctest/task/123456, i.e. the path segments, which are already URL-encoded, are encoded again.

I would expect Asana to POST events to the URL as stated in the webhook and I consider this a bug.

We have integrated various services via webhooks. This is the first time we’re seeing such unfortunate behavior, and it’s pretty cumbersome to have special treatment like this. Can we get this fixed please?

I’d appreciate a response and a timeline :smiley:
Thank you in advance

Matthias from Bardeen.ai

Hi @Integrations2,

Thanks for bringing this up. I have flagged this to our engineering team. I’ll let you know if we’re given a timeline for the fix.

Out of curiosity could you explain more about your use case? I am curious why you encode your target URL before calling Establish a webhook. I am guessing your original URL looks like webhooks/json/asana/some|test/task/123456 then you encode it to look like webhooks/json/asana/some%7Ctest/task/123456 and call Establish a webhook.

Hi John, thanks for the swift reply!

I’m happy to elaborate. We at bardeen.ai are building a platform where users can connect various services like Zoom, Asana, and others, and generate automations between these.

Since we integrate many services that do webhooks, our webhooks infrastructure is quite generic in that we don’t create webhooks for us, but on behalf of users who use our platform to build automations. When a user creates an automation that triggers “when a Task is created in Asana”, we register webhook notfications using the user’s token against our server.

There are two main groups of services that send webhooks:

(1) Zoom or HubSpot only allow a single, hardcoded URL, and will send domain specific user account information in “rich” events to (I’m simplifying here) /webhooks/<SID>/. We validate authenticity of received events, and forward as needed. These services btw. will send events for any user who integrated e.g. Zoom with Bardeen, so we need a mechanism to tell apart relevant events from irrelevant ones (i.e.: does the user with whom this event is associated have an automation enabled that needs to run?).

(2) Services like Google or Asana allow us to register dynamic target URLs – and usually don’t send any information in the payload that would allow us to associate an event with a user in order to check that we expect that event and need to process it.

On top of these differences, we need a way to register and deregister webhook notifications based on the existence and absence, respectively, of automations: we “subscribe” to certain events when an automation is enabled, and unsubscribe when no automation requires these events any longer.

In order to handle these requirements, our infrastructure tracks what we call subscriptions. A subscription has a channel and subject, and is created in our system when a user activates an automation.

For cases like Zoom or HubSpot, we have custom route handlers which inspect the payload and will derive the channel based on the given information. For services that allow dynamic target URLs, we encode channel information into the URL. When we receive an event against a URL that was dynamically registered, we derive the channel from that URL, which is the case for Asana. All of these route handlers will forward the received event along with channel information, and a step later in that pipeline will discard events for whose channels no subscription exists.

So, in the example provided we have webhooks/json/asana/<channel>. Since the channel must allow to encode arbitrary information, it contains separators and special character of all sorts, and we url-encode the path segments in order to have a valid URL.

I hope this clarifies why we’re doing what we’re doing. We could have used any other url safe encoding for channels, but went with url-encoding since it seemed the most obvious and is shortest.

Hi @Integrations2,

Thank you for the detailed explanation. This is very helpful in understanding the context. I’ll pass this along to our engineering team.

I assume your custom route handlers could decode the POST URL sent by Asana to your app server to extract the necessary information from the route as a a way to unblock yourself.

Hi John,
yes, exactly this is what we’re doing right now.

thanks again
Matthias