To handle Paystack webhooks in Go, create an HTTP handler that reads the raw body with io.ReadAll, computes an HMAC SHA-512 hash using crypto/hmac, compares it to the X-Paystack-Signature header with hmac.Equal, and processes the event. Return a 200 status.
import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
signature := r.Header.Get("X-Paystack-Signature")
if signature == "" {
http.Error(w, "Missing signature", http.StatusBadRequest)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
mac := hmac.New(sha512.New, []byte(paystackSecret))
mac.Write(body)
expectedMAC := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expectedMAC), []byte(signature)) {
http.Error(w, "Invalid signature", http.StatusBadRequest)
return
}
var event struct {
Event string "json:\"event\""
Data json.RawMessage "json:\"data\""
}
json.Unmarshal(body, &event)
// Process async
go processEvent(event.Event, event.Data)
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func processEvent(eventType string, data json.RawMessage) {
switch eventType {
case "charge.success":
var txn struct {
Reference string "json:\"reference\""
}
json.Unmarshal(data, &txn)
if txn.Reference != "" {
if err := verifyAndFulfill(txn.Reference, db); err != nil {
log.Printf("Fulfillment error: %v", err)
}
}
case "subscription.create":
log.Println("New subscription created")
case "invoice.payment_failed":
log.Println("Invoice payment failed")
default:
log.Printf("Unhandled event: %s", eventType)
}
}
func main() {
http.HandleFunc("/api/pay", initializeHandler)
http.HandleFunc("/payment/callback", callbackHandler)
http.HandleFunc("/webhooks/paystack", webhookHandler)
log.Println("Server running on :8080")
http.ListenAndServe(":8080", nil)
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
func TestWebhookHandler(t *testing.T) {
payload := []byte("{\"event\":\"charge.success\",\"data\":{\"reference\":\"test\"}}")
mac := hmac.New(sha512.New, []byte("sk_test_xxx"))
mac.Write(payload)
sig := hex.EncodeToString(mac.Sum(nil))
req := httptest.NewRequest("POST", "/webhooks/paystack", bytes.NewBuffer(payload))
req.Header.Set("X-Paystack-Signature", sig)
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
webhookHandler(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected 200, got %d", rr.Code)
}
}
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course