Verify webhooks in Go

The github.com/watenga/watenga-go module ships a webhook package that verifies the Watenga-Signature header and returns the parsed event.

Install

bash
go get github.com/watenga/watenga-go

ConstructEvent

ConstructEvent(rawBody []byte, signatureHeader, secret string, toleranceSeconds int) returns the decoded event (map[string]any) and an error. It recomputes HMAC-SHA256 over `${t}.${rawBody}`, compares it to v1 in constant time, and rejects timestamps outside the tolerance window.

HTTP handler

go
package main

import (
	"io"
	"net/http"
	"os"

	"github.com/watenga/watenga-go/webhook"
)

func watengaWebhook(w http.ResponseWriter, r *http.Request) {
	rawBody, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "cannot read body", http.StatusBadRequest)
		return
	}

	// ConstructEvent verifies the Watenga-Signature header
	// (t=<unix>,v1=<hmac-sha256-hex>) and the 5-minute tolerance window.
	// Pass 0 for toleranceSeconds to use the 300s default.
	event, err := webhook.ConstructEvent(
		rawBody,
		r.Header.Get("Watenga-Signature"),
		os.Getenv("WATENGA_WEBHOOK_SECRET"),
		0,
	)
	if err != nil {
		http.Error(w, "invalid signature", http.StatusBadRequest)
		return
	}

	// event is the parsed envelope: { id, object, type, created, data }
	if event["type"] == "payment.capture.completed" {
		data, _ := event["data"].(map[string]any)
		// fulfil the order using data["id"]
		_ = data
	}

	w.WriteHeader(http.StatusOK)
}

Use the raw body

Always pass the unparsed request body to ConstructEvent. Any re-serialisation changes the bytes and breaks signature verification. See the Webhooks reference for the signing scheme.