XeroAPI / xerogolang

Golang SDK for the Xero API
MIT License
24 stars 29 forks source link

Webhook support is missing #17

Open maxannear opened 6 years ago

maxannear commented 6 years ago

This SDK doesn't have any support for web-hooks.

Fortunately its pretty easy to implement. The documentation about validating the x-xero-signature is as good as useless but hopefully the code below will help any future implementors get it sorted! I just ported the .NET code to Go and its working correctly.

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
    "io/ioutil"
    "net/http"
)

func XeroWebhookHandler(w http.ResponseWriter, r *http.Request) {

    xeroSignature := r.Header.Get("x-xero-signature")
    if xeroSignature == "" {
        http.Error(w, "Xero webhook endpoint got hit without a 'x-xero-signature' header", 400)
        return
    }

    defer r.Body.Close()
    //Because this endpoint will be unauthenticated, be careful reading the entire body
    //To minimise any DDoS risk only read up to a certain size (50kB)
    bodyReaderWithSizeLimit := http.MaxBytesReader(w, r.Body, 50*1024)
    bodyBytes, err := ioutil.ReadAll(bodyReaderWithSizeLimit)
    if err != nil {
        http.Error(w, "webhook had unreadable body.", 500)
        return
    }

    //Store the xeroWebhookKey somewhere safe
    xeroWebhookKey := "Get this value from the webhook config on developer.xero.com"
    expectedSignature := GetExpectedXeroSignature(bodyBytes, xeroWebhookKey)

    signatureGood := xeroSignature == expectedSignature
    if !signatureGood {
        fmt.Println("Xero Failed to verify xero signature")
        w.WriteHeader(401)
        return
    }

    //Process webhook
    fmt.Println("Xero webhook recieved")
    fmt.Println(string(bodyBytes))

    w.WriteHeader(200)
}

func GetExpectedXeroSignature(requestBodyBytes []byte, clientSecret string) string {
    key := []byte(clientSecret)
    hash := hmac.New(sha256.New, key)
    hash.Write(requestBodyBytes)
    return base64.StdEncoding.EncodeToString(hash.Sum(nil))
}