Last active
January 25, 2023 09:43
-
-
Save haseebq/adc51aaeb4e612c205291a411a7a8872 to your computer and use it in GitHub Desktop.
Golang code to verify Paddle's webhook signature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"crypto" | |
"crypto/rsa" | |
"crypto/sha1" | |
"crypto/x509" | |
"encoding/base64" | |
"encoding/pem" | |
"net/url" | |
"sort" | |
"bytes" | |
"strconv" | |
"errors" | |
) | |
// VerifyPaddleSig verifies the p_signature parameter sent | |
// in Paddle webhooks. 'values' is the decoded form values sent | |
// in the webhook response body. You can get 'values' from a | |
// http.Request by calling request.Form() | |
func VerifyPaddleSig(values url.Values, publicKeyPEM string) error { | |
der, _ := pem.Decode([]byte(publicKeyPEM)) | |
if der == nil { | |
return errors.New("Could not parse public key pem") | |
} | |
pub, err := x509.ParsePKIXPublicKey(der.Bytes) | |
if err != nil { | |
return errors.New("Could not parse public key pem der") | |
} | |
signingKey, ok := pub.(*rsa.PublicKey) | |
if !ok { | |
return errors.New("Not the correct key format") | |
} | |
sig, err := base64.StdEncoding.DecodeString(values.Get("p_signature")) | |
if err != nil { | |
return err | |
} | |
// Delete p_signature | |
values.Del("p_signature") | |
// Sort the keys | |
sortedKeys := make([]string, 0, len(values)) | |
for k := range values { | |
sortedKeys = append(sortedKeys, k) | |
} | |
sort.Strings(sortedKeys) | |
// Php Serialize in sorted order | |
var sbuf bytes.Buffer | |
sbuf.WriteString("a:") | |
sbuf.WriteString(strconv.Itoa(len(sortedKeys))) | |
sbuf.WriteString(":{") | |
encodeString := func(s string) { | |
sbuf.WriteString("s:") | |
sbuf.WriteString(strconv.Itoa(len(s))) | |
sbuf.WriteString(":\"") | |
sbuf.WriteString(s) | |
sbuf.WriteString("\";") | |
} | |
for _, k := range sortedKeys { | |
encodeString(k) | |
encodeString(values.Get(k)) | |
} | |
sbuf.WriteString("}") | |
sha1Sum := sha1.Sum(sbuf.Bytes()) | |
err = rsa.VerifyPKCS1v15(signingKey, crypto.SHA1, sha1Sum[:], sig) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
const DefaultPaddlePublicKey = ` | |
-----BEGIN PUBLIC KEY----- | |
YOUR PUBLIC KEY | |
-----END PUBLIC KEY-----` | |
func main() { | |
testReq := `alert_name=subscription_created&cancel_url=https%3A%2F%2Fcheckout.paddle.com%2Fsubscription%2Fcancel%3Fuser%3D5%26subscription%3D2%26hash%3Dea8aec0ed511b91f58b44c5c7e519891de2e301e&checkout_id=2-d1bab8dbfc11d77-157eb37302¤cy=USD&email=blanca66%40example.org&event_time=2018-10-30+19%3A55%3A06&marketing_consent=&next_bill_date=2018-11-05&passthrough=UserID&quantity=90&status=active&subscription_id=1&subscription_plan_id=4&unit_price=unit_price&update_url=https%3A%2F%2Fcheckout.paddle.com%2Fsubscription%2Fupdate%3Fuser%3D7%26subscription%3D6%26hash%3De88ee8ac9ef0ba6bd2fcdc577524030f02f14f67&p_signature=yDkylvQGOSkonSN8BcQaG8YIedGbnQOo8mvmpGKSIwbJlcymtQqtmzBPauCo2pvMnUn5H7XcYjUxIAD%2F2JqgtBA%2BY3GWahqEJBg2SPWjqUdqQkjaCwJ9KEZgpTg%2BddNXp5AVFklAaiZia9JpdqMlMDm%2B5AFCJ2F6W6KE2ehPR0%2B22VrtvNAikOJwr7Jq53GiSPDT9p9vWHY6Z%2FzTLPOGhnXoULZwLwIRWBQBk8lAcv13UdX5pErP3GvC4pbv53Pa1CUZrmx1MjsYG%2BgAMdZbmlYXXjmQfl9CnRc5qmus6CSOKrO7wKqQ0NrIoiQc8RoZPP47Qqc8OlVHhHei2TwejfcVQpIouUxnIjBCKaznu5bGFBuWj328WpKmrtUuLgk5SGZTjq6PPbsXr5yAML60Vl33TUfmz95Td3BsLJ9jzb4SXz4SCcVPu6BK7wP%2FWl8AcRrU9o7en6zIRHyjU3yrDWLoQaIm%2FvxbdSYv5AOrHQoy%2FZ82XOB%2FRFYANdKeuQoTpy51MriOB7qZQK%2FPAd98uQxHlTz2f%2BX5LvLyz6jLjGMmbfMgX9QoXJ3UN5f6R3czTbfqOo%2BQc0NE8hyWB5%2FjHgEOeEuPGTe%2BXNhluHb3tKgjN8cK6LuJI56d5wIWwkMm%2BkXbkHYjfxo1Z746mXbY6CLkfD3hVf5fsOyl47mPTEs%3D` | |
values, _ := url.ParseQuery(testReq) | |
fmt.Print(VerifyPaddleSig(values, DefaultPaddlePublicKey)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
<3