Xero - Intent To Receive Ruby

394 Views Asked by At

This is the params coming from Xero

{"events"=>nil,
 "firstEventSequence"=>0,
 "lastEventSequence"=>0,
 "entropy"=>"KFDXIMNLPDAMRBOEVAVF",
 "controller"=>"admin/billing/webhooks",
 "action"=>"handle_hook",
 "webhook"=>
  {"events"=>nil,
   "firstEventSequence"=>0,
   "lastEventSequence"=>0,
   "entropy"=>"KFDXIMNLPDAMRBOEVAVF"}}

I want to verify Intent To Receive https://developer.xero.com/documentation/webhooks/configuring-your-server#intent

data = {"events"=>nil, "firstEventSequence"=>0, "lastEventSequence"=>0, "entropy"=>"KFDXIMNLPDAMRBOEVAVF"}

Key = HRj6QPub9BNE4MWewrcLrkKFjpiikV1KrlMZCvDawyDR95MGkkuE2y1DXFP1tifsEWaJygLx6zG0r9rXVTflcg==

tried below

Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, data)).strip()

*** TypeError Exception: no implicit conversion of ActionController::Parameters into String

OR

hash  = OpenSSL::HMAC.digest('sha256', key, data)
*** TypeError Exception: no implicit conversion of ActionController::Parameters into String

How to achive this here?

To ensure the requests you receive are coming from Xero you need to verify the signature provided in the x-xero-signature header. When creating or re-enabling a webhook subscription (or updating the subscription url) the user will be prompted to start an 'Intent To Receive' validation. This validation process will be a series of HTTPS POST requests to the url provided in the subscription.

EDIT Rails was converting blank array into nil, so I made payload by myself to look exactly the same according to documentation

data = params[:webhook]

payload = {
             "events": [],
             "lastEventSequence": data[:lastEventSequence],
             "firstEventSequence": data[:firstEventSequence],
             "entropy": data[:entropy]
          }
2

There are 2 best solutions below

0
On BEST ANSWER

After researching and debugging code finally find a way to do this

  def handle_hook
    key = ENV['XERO_WEBHOOK_KEY']
    payload = request.body.read
    calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest('sha256', key, payload))
    if calculated_hmac.strip() == request.headers['x-xero-signature']
      head :ok
    else
      head :unauthorized
    end
  end

Using Strip() is magical here

1
On

data should be a string.

digest = OpenSSL::Digest::Digest.new('sha256')
hmac_digest = OpenSSL::HMAC.digest(digest, key, data['firstEventSequence'].to_s)
Base64.encode64(hmac_digest).strip() => "JvORQ/sWHvAXO/3nm9vG7+VgAqA93bTSMMIbVHIRgnM="