1

I'm trying to setup a webhook on Asana with the following:

token = <user_token>
uri = URI.parse("https://app.asana.com/api/1.0/webhooks")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{token}"
request.set_form_data(
  "resource" => "219668070168571",
  "target" => "https://myserveraddress/api/webhooks/asana/1234",
)

req_options = {
  use_ssl: uri.scheme == "https",
}

response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
  http.request(request)
end

puts response.body
puts response.code

And this is my controller handling the response:

response.set_header("X-Hook-Secret", request.headers["X-Hook-Secret"])
head 200

And when I do a curl request such as:

curl -i --header "X-Hook-Secret: 12356789" https://myserveraddress/api/webhooks/asana/1234

I get the following answer:

HTTP/1.1 200 OK
X-Hook-Secret: 12356789
Content-Type: text/html
Cache-Control: no-cache
X-Request-Id: fd8ec280-9ef1-426c-9cb5-58309f835ccf
X-Runtime: 0.045875
Vary: Origin
Transfer-Encoding: chunked

But when I try to setup the webhook I get this response from Asana:

{"errors":[{"message":"Could not complete activation handshake with target URL. Please ensure that the receiving server is accepting connections and supports SSL","help":"For more information on API status codes and how to handle them, read the docs on errors: https://asana.com/developers/documentation/getting-started/errors"}]}

What am I missing here?

WagnerMatosUK
  • 4,309
  • 7
  • 56
  • 95

1 Answers1

1

Hey there @WagnerMatosUK,

Your response looks good to me, so that's probably not the problem. I have a suspicion of what's happening, though...

The ordering of the webhook handshake goes like this:

client                    server
|-- POST /api/1.0/webhooks --->|
                               |
|<--- POST {yourcallback} ---- |
|                              |
|--- 200 OK [with secret] ---> |
                               |
<--------- 200 OK -------------|

That is, the callback happens inside of the original request before the original request returns.

This is great for getting assurance that the webhook is established, because the return from the first request will be either success or failure depending on what happened with the handshake. However, this also means that your server needs to be able to respond to the incoming handshake request before the first request returns.

This has implications for single-threaded servers that block on the return of the first request - namely, they are blocked! They can't respond to the second request until that first request returns.

The solution is to kick off the first request in a thread, which frees up the second thread to occur, or to run multiple servers in parallel somehow (like Unicorn does) so that a server in process 2 can handle the handshake while process 1 is blocked.

Hopefully this makes sense and solves the issue! Cheers!

Matt
  • 10,434
  • 1
  • 36
  • 45
  • Hi Matt. Thank for your reply and sorry its taken me so long to comment on it. Your reply does make sense, however my server is already threaded (I use Puma), meaning that the original request is not blocking the second. – WagnerMatosUK Aug 15 '17 at 06:52