Fill in the form to arrange a demo

Verifying Webhooks

With each webhook (for both Alerts/Events and Fulfillments), we send a signature field that can be used to verify that the webhook was sent by Paddle.

We use public/ private key encryption to allow you to verify these requests. The information below should be a step-by-step guide on how to verify a Paddle signature.

1 Getting Your Public Key

We list your public key on the public key page in your Paddle dashboard.

Your public key should look similar to the key below. (Note the key below is not your key.)

-----END PUBLIC KEY-----

2 Getting the Signature

Each webhook sends the signature that you’ll be verifying as a parameter called p_signature.

3 Verifying the Signature

The process of verifying the signature is simple. The signature is comprised of a signed set of all of the fields and their values within the request you have been sent. The first step is to remove the p_signature field, as that isn’t included within the signature verification. We then ksort() the fields so they’re in the same order, followed by serializing and signing the resulting array.

  // Your Paddle 'Public Key'
  $public_key = '-----BEGIN PUBLIC KEY-----
  // Get the p_signature parameter & base64 decode it.
  $signature = base64_decode($_POST['p_signature']);
  // Get the fields sent in the request, and remove the p_signature parameter
  $fields = $_POST;
  // ksort() and serialize the fields
  foreach($fields as $k => $v) {
	  if(!in_array(gettype($v), array('object', 'array'))) {
		  $fields[$k] = "$v";
  $data = serialize($fields);
  // Verify the signature
  $verification = openssl_verify($data, $signature, $public_key, OPENSSL_ALGO_SHA1);
  if($verification == 1) {
	  echo 'Yay! Signature is valid!';
  } else {
	  echo 'The signature is invalid!';

# Your Paddle public key.
public_key = '''-----BEGIN PUBLIC KEY-----

import collections
import base64

# Crypto can be found at
from Crypto.Signature import pkcs1_15
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA
import hashlib

# PHPSerialize can be found at
import phpserialize

# Convert key from PEM to DER - Strip the first and last lines and newlines, and decode
public_key_encoded = public_key[26:-25].replace('\n', '')
public_key_der = base64.b64decode(public_key_encoded)

# input_data represents all of the POST fields sent with the request
# Get the p_signature parameter & base64 decode it.
signature = input_data['p_signature']

# Remove the p_signature parameter
del input_data['p_signature']

# Ensure all the data fields are strings
for field in input_data:
	input_data[field] = str(input_data[field])

# Sort the data
sorted_data = collections.OrderedDict(sorted(input_data.items()))

# and serialize the fields
serialized_data = phpserialize.dumps(sorted_data)

# verify the data
key = RSA.importKey(public_key_der)
digest =
verifier =
signature = base64.b64decode(signature)
if verifier.verify(digest, signature):
	print("Yay! Signature is valid!");
	print("The signature is invalid!");

// Node.js & 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) && (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');

    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 */'/', function(req, res, next) {

module.exports = router;

Karl has kindly shared a Java helper class for webhook verification on Github and thanks to Drew for sharing a server side Swift gist. We also now have an C# ASP.NET example thanks to Jamie.

Questions about Paddle?

If you need any help regarding your Paddle integration, please get in touch with our Customer Success team using the form below.

Questions about Paddle?