{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/verify-paddle-webhooks-in-ruby/","result":{"data":{"site":{"siteMetadata":{"title":"The Official MailPace Blog","author":"Paul, founder of MailPace"}},"mdx":{"id":"84029c61-bd80-58ca-9538-b0bf532ff2e3","excerpt":"When we first launched  MailPace  we chose  Paddle  as our Payments Provider, primarily because they handle all sales taxes and payment infrastructure globally…","body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"How to Verify Paddle.com Webhooks / Alerts in Ruby on Rails\",\n  \"date\": \"2021-10-05T08:00:03.284Z\",\n  \"description\": \"A short guide to verifying payment provider Paddle.com webhooks and alerts in Ruby on Rails\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"When we first launched \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://mailpace.com\"\n  }, \"MailPace\"), \" we chose \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://paddle.com\"\n  }, \"Paddle\"), \" as our Payments Provider, primarily because they handle all sales taxes and payment infrastructure globally. One of the things that took longer than it should have was ensuring that alerts (webhooks) received from paddle.com actually come from Paddle. \"), mdx(\"p\", null, \"Luckily Paddle signs every request using \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://en.wikipedia.org/wiki/Public-key_cryptography\"\n  }, \"Public Key Cryptography\"), \", and it works in a similar way to \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://blog.mailpace.com/blog/whats-a-DKIM-record/\"\n  }, \"DKIM\"), \". Paddle creates a short signature, using a Private Key specific to our Paddle account, and includes it with every webhook sent from their system, which we can verify on our end using the Public Key (see \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://developer.paddle.com/webhook-reference/verifying-webhooks\"\n  }, \"https://developer.paddle.com/webhook-reference/verifying-webhooks\"), \" for more details). \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Without this a nefarious actor might figure out your webhook endpoint and create a bunch of fake subscriptions/updates in your app.\")), mdx(\"p\", null, \"This verification is great, and Paddle has ok docs on how to do it. But I couldn\\u2019t get the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://developer.paddle.com/webhook-reference/verifying-webhooks\"\n  }, \"paddle code examples\"), \" working in the MailPace Rails app without some frustrating trial and error, so here\\u2019s an example of how you can implement Paddle webhook endpoints with verification in Ruby on Rails. Broadly this should apply to any language as well.\"), mdx(\"h1\", null, \"Dependencies\"), mdx(\"p\", null, \"You\\u2019ll need to ensure you have the following dependencies available in your \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Gemfile\"), \" (OpenSSL and Base64 should already be in Rails):\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"php-serialize\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"openssl\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"base64\"))), mdx(\"h1\", null, \"Code!\"), mdx(\"h4\", null, mdx(\"strong\", {\n    parentName: \"h4\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"app/controllers/api/paddle_controller.rb\"))), mdx(\"pre\", {\n    \"className\": \"grvsc-container default-dark\",\n    \"data-language\": \"ruby\",\n    \"data-index\": \"0\"\n  }, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"grvsc-code\"\n  }, mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# A standard Rails API endpoint definition\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"class\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"Api::PaddleController\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" < \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"ActionController::API\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Ensure every request is validated, except when testing\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  before_action :verify_webhook, unless: \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk11\"\n  }, \"->\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" { \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk12\"\n  }, \"ENV\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"[\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"RAILS_ENV\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"] == \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"test\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" }\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Select the right method depending on the webhook sent by paddle, see full list here https://developer.paddle.com/webhook-reference/\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"def\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk11\"\n  }, \"paddle\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"case\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" params[\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"alert_name\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"]\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"when\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"subscription_created\\\"\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"      subscription_created\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"when\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"subscription_payment_succeeded\\\"\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"      subscription_payment_success\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"else\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"      render(\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"        json: { error: \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"alert_name \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk4\"\n  }, \"#{\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"params[\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"'alert_name'\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"]\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk4\"\n  }, \"}\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \" does not match a known webhook / alert\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" },\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"        status: :not_found\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"      )\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"end\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"end\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"def\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk11\"\n  }, \"subscription_created\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Application logic here (e.g. update user account)\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"end\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"def\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk11\"\n  }, \"subscription_payment_success\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Application logic here (e.g. update user account)\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"end\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk4\"\n  }, \"private\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# The actual verification takes place below\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"def\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk11\"\n  }, \"verify_webhook\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Copy and paste from https://vendors.paddle.com/public-key\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# You should store this in an environment variable in a real app, and note the line breaks / formatting which must match exactly\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    public_key = \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"-----BEGIN PUBLIC KEY-----\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"-----END PUBLIC KEY-----\\\"\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# We take all the params available as JSON structure\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    data = accept_all_params.as_json\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Extract the signature itself to verify later\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    signature = \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"Base64\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \".decode64(data[\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"p_signature\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"])\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Remove the unsigned params (the signature itself and additional params from Rails)\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    data.delete(\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"p_signature\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \")\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    data.delete(\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"controller\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \")\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    data.delete(\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"action\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \")\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Sort & serialize params to match the original way Paddle signs the request\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    data.each { |\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk12\"\n  }, \"key\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \", \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk12\"\n  }, \"value\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"| data[key] = \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk12\"\n  }, \"String\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"(value) }\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    params_sorted = data.sort_by { |\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk12\"\n  }, \"key\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \", \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk12\"\n  }, \"_value\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"| key }\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    params_serialized = \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"PHP\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \".serialize(params_sorted, \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk4\"\n  }, \"true\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \")\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# Verify the params and respond with 403 if verification fails\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    digest = \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"OpenSSL\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"::\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"Digest\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \".\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk4\"\n  }, \"new\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"(\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk8\"\n  }, \"\\\"SHA1\\\"\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \")\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    pub_key = \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"OpenSSL\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"::\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"PKey\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"::\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk10\"\n  }, \"RSA\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \".\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk4\"\n  }, \"new\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"(public_key).public_key\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"return\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" head(\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk7\"\n  }, \"403\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \") \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"unless\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" pub_key.verify(digest, signature, params_serialized)\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"end\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"def\"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \" \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk11\"\n  }, \"accept_all_params\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# We do this because paddle has a p_signature, and if they add extra params in the future\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk3\"\n  }, \"# we need to ensure the signature still validates\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"    params.permit!\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk1\"\n  }, \"  \"), mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"end\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }, mdx(\"span\", {\n    parentName: \"span\",\n    \"className\": \"mtk15\"\n  }, \"end\")), \"\\n\", mdx(\"span\", {\n    parentName: \"code\",\n    \"className\": \"grvsc-line\"\n  }))), mdx(\"p\", null, \"That\\u2019s it - easy when you know how.\"), mdx(\"style\", {\n    \"className\": \"grvsc-styles\"\n  }, \"\\n  .grvsc-container {\\n    overflow: auto;\\n    -webkit-overflow-scrolling: touch;\\n    padding-top: 1rem;\\n    padding-top: var(--grvsc-padding-top, var(--grvsc-padding-v, 1rem));\\n    padding-bottom: 1rem;\\n    padding-bottom: var(--grvsc-padding-bottom, var(--grvsc-padding-v, 1rem));\\n    border-radius: 8px;\\n    border-radius: var(--grvsc-border-radius, 8px);\\n    font-feature-settings: normal;\\n  }\\n  \\n  .grvsc-code {\\n    display: inline-block;\\n    min-width: 100%;\\n  }\\n  \\n  .grvsc-line {\\n    display: inline-block;\\n    box-sizing: border-box;\\n    width: 100%;\\n    padding-left: 1.5rem;\\n    padding-left: var(--grvsc-padding-left, var(--grvsc-padding-h, 1.5rem));\\n    padding-right: 1.5rem;\\n    padding-right: var(--grvsc-padding-right, var(--grvsc-padding-h, 1.5rem));\\n  }\\n  \\n  .grvsc-line-highlighted {\\n    background-color: var(--grvsc-line-highlighted-background-color, transparent);\\n    box-shadow: inset var(--grvsc-line-highlighted-border-width, 4px) 0 0 0 var(--grvsc-line-highlighted-border-color, transparent);\\n  }\\n  \\n  .default-dark {\\n    background-color: #1E1E1E;\\n    color: #D4D4D4;\\n  }\\n  .default-dark .mtk3 { color: #6A9955; }\\n  .default-dark .mtk15 { color: #C586C0; }\\n  .default-dark .mtk1 { color: #D4D4D4; }\\n  .default-dark .mtk10 { color: #4EC9B0; }\\n  .default-dark .mtk11 { color: #DCDCAA; }\\n  .default-dark .mtk12 { color: #9CDCFE; }\\n  .default-dark .mtk8 { color: #CE9178; }\\n  .default-dark .mtk4 { color: #569CD6; }\\n  .default-dark .mtk7 { color: #B5CEA8; }\\n\"));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"title":"How to Verify Paddle.com Webhooks / Alerts in Ruby on Rails","date":"October 05, 2021","description":"A short guide to verifying payment provider Paddle.com webhooks and alerts in Ruby on Rails"}}},"pageContext":{"slug":"/verify-paddle-webhooks-in-ruby/","previous":{"fields":{"slug":"/free-transactional-email-templates/"},"frontmatter":{"title":"Free Transactional Email Templates"}},"next":{"fields":{"slug":"/what-is-reverse-dns-rdns/"},"frontmatter":{"title":"What is Reverse DNS (rDNS) and how does it work?"}}}},"staticQueryHashes":["1956864136","63159454"]}