Validating CRC for Webhooks



The current documentation for securing webhooks is ambiguous. Here’s what I’m doing in Ruby.

  1. Retrieving the passed in crc_token
  2. Creating the digest (digest =
  3. Generating the HMAC hmac = OpenSSL::HMAC.hexdigest(digest, key, crc_token) where the key is the consumer secret
  4. Returning the following json with the hmac
    { "response_token": "sha256=THE HMAC" }
  5. I still get the Webhook url does not match requirements.

How to register a webhook url in Twitter?
Account activity API errors":[{"code":200,"message":"Forbidden or "code":32,"message":"Could not authenticate you."

Hey @kimenye, it loos like you may need to base64 encode the hmac.

Here is an example for ruby, along with a bunch of other languages:

Also, make sure you return a 200 response code and you have latency below 1 second.

Invalid response token

After THE HMAC, there is no need for an ‘=’ char.
“response_token”: “sha256=THE HMAC”


Thanks @stoney024 I figured it out. My main issue issue was with skipping the base64 encoding.


@joncipriano, @kimenye, or @stoney024 (or anyone) - would you mind posting sample Ruby code for this? I can’t seem to get my code right. It’s responding 200 and definitely less than a second but my url still fails.

This is what I’m trying:

digest =
hmac = OpenSSL::HMAC.hexdigest(digest,
crc_response = "sha256=" + Base64.encode64(hmac)

Then the JSON response as follows:

format.json { render status: :ok, json: { response_token: crc_response } }

For example, if my consumer secret was “MY SECRET” and the CRC token was “some_crc_token”, then this code yields:


But when using the actual consumer secret this fails with Webhook URL does not meet the requirements.

Could you (or anyone) post some working sample Ruby code and/or some examples of CRC token, fake consumer secrets, and expected response token so I can verify if the algorithm is or is not correct? Sample Ruby code appreciated.



@HeroicSocialApp This is what’s working for me right now.
digest = OpenSSL::HMAC.digest('sha256', <CONSUMER_SECRET>, params[:crc_token]) crc_response = Base64.encode64(digest).strip