Sep 16, 2022

Verifying a Paddle Webhook using Elixir

Here's a quick snippet that will save developers using Paddle and Elixir many hours of frustration (trust me, I suffered through them).

This function validates the raw data of a webhook request from Paddle.com. It's based on the code officially provided here.

It requires one dependency, php_serialize.

@doc """
Verifies a Paddle webhook connection.
https://developer.paddle.com/webhook-reference/verifying-webhooks
"""
def verify_webhook(raw) do
    # We take the 'data' key from the response.
    signature = Base.decode64!(Map.get(raw.data, :p_signature, ""))

    # This requires an ordered map to validate
    # Maps in Elixir are ordered under n<32 items
    # We assume that the length is less than 32
    # Otherwise, just use an OrdMap implementation
    raw =
        Map.get(raw, :data)
        |> Map.delete(:p_signature)
        |> Map.new(fn {k, v} -> {k, to_string(v)} end)

    # Paddle weirdly requires this format to validate their requests...
    serialized = PhpSerializer.serialize(raw)

    # We get the Public Key from the application settings.
    str_pkey = Application.get_env(:cashflow, Cashflow.Billing)[:paddle_pkey]

    [decoded_pkey] = :public_key.pem_decode(str_pkey)
    decoded_pkey = :public_key.pem_entry_decode(decoded_pkey)
    :public_key.verify(serialized, :sha, signature, decoded_pkey)
end

To use this, either hard-code the public key or open your application's config and add an entry:

# Make sure to include the new lines
config :app, App.Billing, paddle_pkey: "-----BEGIN PUBLIC KEY------..."

All done. You're all set to start charging people for stuff!