Securing Webhook Initial Handshake

Hello,

There’s a couple of things I haven’t been able to find any documentation on:

  1. Is there any way during the webhook establishment to get the API asana to use an authentication token when repeating back the x-hook-secret? I suppose I can set a domain level filter for the response and then rely on the x-hook-secret to maintain a secure connection but I would rather have the API return an authentication token at least during the initial handshake

  2. How often do handshake secrets rotate? In the above case that I cannot secure the transmission from Asana to my API, if the handshake secret rotates frequently then I would have to manually monitor the handshake creation each time to ensure traffic is secure.

Thanks

cc: @John_Vu @Andrew-asana

Hi @Harrison_Reid,

Apologies for the late reply. To answer your question

  1. Is there any way during the webhook establishment to get the API asana to use an authentication token when repeating back the x-hook-secret? I suppose I can set a domain level filter for the response and then rely on the x-hook-secret to maintain a secure connection but I would rather have the API return an authentication token at least during the initial handshake

Our API does not return an authentication token or support sending one along with the x-hook-secret. It would be best to rely on the x-hook-secret for a secure connection.

  1. How often do handshake secrets rotate? In the above case that I cannot secure the transmission from Asana to my API, if the handshake secret rotates frequently then I would have to manually monitor the handshake creation each time to ensure traffic is secure.

The x-hook-secret does not rotate it’s meant to be stored and used to generate a computed signature with the event body to validate the x-hook-signature. So each time you get a webhook event it’ll come with an x-hook-signature that you are supposed to verify. This x-hook-signature is most likely different for each event since it’s computed using the x-hook-secret and event body. See the our Receiving events doc for more details.

Also, we recently introduced an AI chatbot in our developer docs. Feel free to ask it questions. I asked it your questions and the answer it gave aligns with what I’ve said.

Hey John,

Thanks for the answer to number 2.

For number 1. this means we would have to expose an API for the initial handshake (i.e we send out a request to establish the webhook, but we receive the secret for the first time). So we would have a fully exposed API for that initial handshake but then it would be secured by the secret. I asked your LLM chatbout about whitelisting your IP ranges but it said I need to contact support. Understood that after the initial handshake we’re relatively secure (we check the x-hook-secret against the one were sent and anything that doesn’t have it or doesn’t match we throw out) but this setup is unsecure unless we whitelist the incoming IP ranges for the webhook secret response.

If we cannot setup a whitelist on our side for the initial handshake, spinning up new webhooks is going to be a manual process. We’ll have to monitor requests while doing it, make sure that only the ones we expect come in and then be fine.

Thanks for the details @Harrison_Reid. I can see why you are concerned. I don’t have a solution in mind at the moment, but I can ask around to see if others have ideas/recommendations on securing the initial handshake. We use AWS lambda functions for our webhooks. If you would like to do whitelisting you can see the range of addresses in the AWS docs AWS IP address ranges - Amazon Virtual Private Cloud. Whitelisting the range of IP addresses isn’t the most ideal but it’s better than allowing any IP address.

Hi @Harrison_Reid, I talked to our engineer and Solutions team and they have a few recommendations on making the initial handshake more secure. Here are some things you can do:

Minimizing the attack surface:

  • Your webhook server should only listen for and respond to a handshake request during the period when they have an outbound request to Asana to set a webhook (Establish a webhook ). This won’t eliminate the attack of someone trying to setup a fake webhook handshake during that time window, but significantly limits the surface area.

Using Target URL query params (similar to your idea for #1)

  1. On your webhook server generate a one-time code for webhook setup
  2. Pass in your one-time code in the URL parameters for the target when you make a call to Establish a webhook
  3. Asana will make a call to your target URL that will contain your one-time code along with the x-hook-secret during the initial handshake
  4. Your app server reads the one-time code and checks that it’s the one you sent

Discord engineer here. This is an unacceptable and absurd security posture from Asana. There is no way to verify that initial payload is coming from Asana; it could come from anywhere and trick a server into trusting future payloads.

This setup also requires using some kind of statefulness in an Asana webhook receiver, which makes it quite difficult to use something like a Cloudflare worker or Lambda function.

Ideally, Asana would do what the vast majority of other API providers do here, and allow the user to establish a secret when they create the webhook (or at least tell the user what the randomly generated secret is). Not send a secret to the very endpoint that is supposed to only accept signed requests.

Hi @anon79756254 and welcome!

This webhook enhancement has indeed been raised several times internally and is currently in our backlog. I’ve followed up on this request to refresh the discussion with our team.

We really appreciate your feedback. I’ll keep an eye on this thread and update the community when there’s progress to share.

Best,
Dominik

Hi @anon79756254,

Just following up after discussing with our API team.

I had missed an important detail in the Establish a webhook endpoint
reference
: the X-Hook-Secret is also returned in the API response to POST /webhooks. That response is initiated by you, so you can treat the secret there as the trusted source of truth. The initial handshake request to your target URL still includes the secret, but you don’t need to rely on it for trust. Instead:

  • Use the handshake only to prove that you control the target URL (by echoing the header and returning 200/204).
  • Rely on the secret from the API response for future request verification.
  • Once established, every webhook event includes an X-Hook-Signature (HMAC-SHA256 over the body with the secret). You can compute and verify this signature to authenticate all incoming events.

This addresses the concern that an attacker could spoof the handshake and trick a server into trusting future payloads. Since the secret is obtained from the API response (not from the handshake itself), a forged handshake cannot establish a valid subscription.

For reference, here’s the updated diagram in the Webhooks documentation, which shows the secret being returned in the API response.

Apologies for the earlier confusion, and I hope this clears up the security model. Please let us know if you still see any gaps or have further questions!

Best,
Dominik

That does, admittedly, make a lot more sense in terms of security, but it leaves the second problem: requiring the webhook server to be stateful and to understand Asana’s specific handshake/signature protocol. This means a lot of customization around general purpose integration products like Argo Events, in stark contrast with other webhook sources (including large vendors like Jira and virtually all ISVs).

Hey @Tomer_Gabel,

In the webhook scheme, these could be separate:

  • a client creating a webhook subscription
  • a server which is the target for the webhook events

In the handshake, the target only needs to echo back the value in the header. This is to prevent our service from being used maliciously for spam or denial of service attacks.

From there, you could technically ignore the signature and accept events, but we recommend that you verify the signature at the target server to ensure the payload is from Asana and hasn’t been tampered with. So to verify the signature, the target would need access to the secret.

Is it the initial handshake that you have an issue with because most servers are not pre-configured to do this? You would just prefer that there is no handshake and webhooks are immediately sent to the target?

As you say, the server (or target) still needs to be stateful - it has to be aware of whether it’s handling a handshake or an event. It also has to include Asana-specific verification logic, although that can be considered an optional security layer.

I would indeed prefer no handshake: while I understand your desire to verify ownership of the URL, the process doesn’t provide any value for the customer. I’ve been integrating multiple SaaS products recently and the only ones that require any sort of handshake are the ones with an inherently bidirectional API (e.g. a Slack app using the Events API). Off-the-shelf event servers like Argo Events and to my knowledge n8n do not support this type of protocol, either.