Webhook Signature Verification

All webhook requests sent from Pakk are signed. Here's how to verify the authenticity of the incoming request.

How Requests are Signed

Pakk signs all webhook requests by hashing the request body bytes using HMAC-SHA256 with the webhook signing key (which you can find on the Webhook record in the admin panel) and encoding to Base64. The resulting signature is send in the header X-Pakk-Webhook-Signature.

Verifying the Signature

You should implement verification code that performs the same procedure and compares the result against the received signature. Here's an example of what that code would look like in Go:

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

// SharedSecret is the secret key used for signing and verifying webhook requests.
// In production, securely retrieve this from a configuration file or environment variable.
var SharedSecret = "your-shared-secret"

// VerifyWebhookSignature verifies that the request's payload matches the signature in the header.
func VerifyWebhookSignature(payload []byte, receivedSignature string) bool {
    // Generate the HMAC-SHA256 signature
    hash := hmac.New(sha256.New, []byte(SharedSecret))
    hash.Write(payload)
    expectedSignature := base64.StdEncoding.EncodeToString(hash.Sum(nil))

    // Use hmac.Equal to prevent timing attacks when comparing signatures
    return hmac.Equal([]byte(expectedSignature), []byte(receivedSignature))
}

// WebhookHandler handles incoming webhooks and verifies their signatures.
func WebhookHandler(w http.ResponseWriter, r *http.Request) {
    // Extract the signature from the header
    receivedSignature := r.Header.Get("X-Pakk-Webhook-Signature")
    if receivedSignature == "" {
        http.Error(w, "Missing signature header", http.StatusUnauthorized)
        return
    }

    // Read the raw JSON payload from the request body
    payload, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Failed to read request body", http.StatusInternalServerError)
        return
    }
    defer r.Body.Close() // Make sure to close the body when done reading

    // Verify the signature
    if !VerifyWebhookSignature(payload, receivedSignature) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    // Process the webhook payload as it's been verified successfully
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Webhook received and verified"))
}

Tips and Tools

Last updated