title: How to verify a webhook with Ruby? (In Rails)
body: <p>I'm looking to learn how to verify a <a href="https://paddle.com/docs/reference-verifying-webhooks/#!" rel="nofollow noreferrer">Paddle</a> webhook with Ruby? <a href="https://paddle.com/docs/reference-verifying-webhooks/#!" rel="nofollow noreferrer">Their example has an option on how to do it with PHP, Python and JavaScript, but no Ruby.</a> Any ideas on how to do it? </p>
  
  <p>This following old example <strong>doesn't work:</strong> </p>
  
  <pre><code>require 'base64'
  require 'php_serialize'
  require 'openssl'
  
  
  public_key = '-----BEGIN PUBLIC KEY-----
  MIICIjANBgkqh...'
  
  # 'data' represents all of the POST fields sent with the request.
  # Get the p_signature parameter &amp; base64 decode it.
  signature = Base64.decode64(data['p_signature'])
  
  # Remove the p_signature parameter
  data.delete('p_signature')
  
  # Ensure all the data fields are strings
  data.each {|key, value|data[key] = String(value)}
  
  # Sort the data
  data_sorted = data.sort_by{|key, value| key}
  
  # and serialize the fields
  # serialization library is available here: https://github.com/jqr/php-serialize
  data_serialized = PHP.serialize(data_sorted, true)
  
  # verify the data
  digest    = OpenSSL::Digest::SHA1.new
  pub_key   = OpenSSL::PKey::RSA.new(public_key).public_key
  verified  = pub_key.verify(digest, signature, data_serialized)
  
  if verified
      puts "Yay! Signature is valid!"
  else
      puts "The signature is invalid!"
  end
  </code></pre>
  
  <p>Here is <a href="https://paddle.com/docs/reference-verifying-webhooks/#!" rel="nofollow noreferrer">their example</a> in JS:</p>
  
  <pre><code>// Node.js &amp; Express implementation
  const express = require('express');
  const querystring = require('querystring');
  const crypto = require('crypto');
  const Serialize = require('php-serialize');
  
  const router = express.Router();
  const pubKey = `-----BEGIN PUBLIC KEY-----
  ...
  -----END PUBLIC KEY-----`
  
  function ksort(obj){
      let keys = Object.keys(obj).sort();
      let sortedObj = {};
  
      for (var i in keys) {
        sortedObj[keys[i]] = obj[keys[i]];
      }
  
      return sortedObj;
    }
  
  function validateWebhook(jsonObj) {
      const mySig = Buffer.from(jsonObj.p_signature, 'base64');
      delete jsonObj.p_signature;
      // Need to serialize array and assign to data object
      jsonObj = ksort(jsonObj);
      for (var property in jsonObj) {
          if (jsonObj.hasOwnProperty(property) &amp;&amp; (typeof jsonObj[property]) !== "string") {
              if (Array.isArray(jsonObj[property])) { // is it an array
                  jsonObj[property] = jsonObj[property].toString();
              } else { //if its not an array and not a string, then it is a JSON obj
                  jsonObj[property] = JSON.stringify(jsonObj[property]);
              }
          }
      }
      const serialized = Serialize.serialize(jsonObj);
      // End serialize data object
      const verifier = crypto.createVerify('sha1');
      verifier.update(serialized);
      verifier.end();
  
      let verification = verifier.verify(pubKey, mySig);
  
      if (verification) {
          return 'Yay! Signature is valid!';
      } else {
          return 'The signature is invalid!';
      }
  }
  
  /* Validate a Paddle webhook to this endpoint, or wherever in your app you are listening for Paddle webhooks */
  router.post('/', function(req, res, next) {
      res.send(validateWebhook(req.body));
  });
  
  module.exports = router;
  </code></pre>
  
  <p>How could I verify the webhook with Ruby? Is there an alternative way to verify the webhook? </p>
  
  <p>Here is an example webhook request:</p>
  
  <pre><code>(
      [alert_name] =&gt; subscription_created
      [cancel_url] =&gt; https://checkout.paddle.com/subscription/cancel?user=4&amp;subscription=8&amp;hash=b0bd354fexamplec39b0ff93c917804acf
      [checkout_id] =&gt; 1-61ff5b400-756ea301a9
      [currency] =&gt; USD
      [email] =&gt; wleffler@example.net
      [event_time] =&gt; 2019-08-10 18:33:58
      [marketing_consent] =&gt; 
      [next_bill_date] =&gt; 2019-08-18
      [passthrough] =&gt; 1132
      [quantity] =&gt; 67
      [status] =&gt; active
      [subscription_id] =&gt; 4
      [subscription_plan_id] =&gt; 5
      [unit_price] =&gt; unit_price
      [update_url] =&gt; https://checkout.paddle.com/subscription/update?user=5&amp;subscription=4&amp;hash=e937ed03f1637e45d912f4f4d293a
      [user_id] =&gt; 6
      [p_signature] =&gt; HM2Isn1k6Sy1cKySQGoFH...
  )
  </code></pre>
  
  <hr>
  
  <p>EDIT: </p>
  
  <p>I'm using Ruby 2.5.5 and Ruby on Rails 5. Currently still getting always "false" in the end. I'll go through it on my console:</p>
  
  <p>Here is the (fake) data, that I get in Rails:</p>
  
  <pre><code>data = {
  "alert_id"=&gt;"1", 
  "alert_name"=&gt;"alert_created", 
  "cancel_url"=&gt;"https://...", 
  "checkout_id"=&gt;"1", 
  "user_id"=&gt;"1", 
  "p_signature"=&gt;"fwWXqR9C..."
  } 
  
  public_key = '-----BEGIN PUBLIC KEY-----sDFKJSD2332FKJLWJF......'
  </code></pre>
  
  <p>Then I do the following:</p>
  
  <pre><code>signature = Base64.decode64(data['p_signature'])
  
  data.delete('p_signature')
  
  data.each {|key, value|data[key] = String(value)}
  
  data_sorted = data.sort_by{|key, value| key}
  
  data_serialized = data_sorted.to_json
  
  digest    = OpenSSL::Digest::SHA1.new
  
  pub_key   = OpenSSL::PKey::RSA.new(public_key)
  
  verified  = pub_key.verify(digest, signature, data_serialized)
  </code></pre>
  
  <p>In the end <em>verified</em> is always <em>false</em>. What am I doing wrong? </p>