Skip to content

Instantly share code, notes, and snippets.

@iberflow
Created August 30, 2025 12:04
Show Gist options
  • Select an option

  • Save iberflow/bbbc151838089d302c8ab12aa3d6d50a to your computer and use it in GitHub Desktop.

Select an option

Save iberflow/bbbc151838089d302c8ab12aa3d6d50a to your computer and use it in GitHub Desktop.
package webhooks
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strings"
"time"
)
type verifier struct {
clock func() time.Time
}
type Verifier interface {
Verify(secret, payload []byte, header string) error
}
func NewVerifier() Verifier {
return verifier{clock: time.Now}
}
func (v verifier) Verify(secret, payload []byte, signatureHeader string) error {
if len(secret) == 0 {
return errors.New("verify: secret is empty")
}
if signatureHeader == "" {
return errors.New("verify: signature header is empty")
}
parts := strings.Split(signatureHeader, ",")
var timestampStr, sigHex string
for _, p := range parts {
p = strings.TrimSpace(p)
if strings.HasPrefix(p, "t=") {
timestampStr = strings.TrimPrefix(p, "t=")
} else if strings.HasPrefix(p, "v1=") {
sigHex = strings.TrimPrefix(p, "v1=")
}
}
if timestampStr == "" || sigHex == "" {
return errors.New("verify: malformed signature header")
}
// recompute expected signature
signedPayload := timestampStr + "." + string(payload)
mac := hmac.New(sha256.New, secret)
if _, err := mac.Write([]byte(signedPayload)); err != nil {
return fmt.Errorf("verify: write payload: %w", err)
}
expected := mac.Sum(nil)
got, err := hex.DecodeString(sigHex)
if err != nil {
return fmt.Errorf("verify: invalid hex signature: %w", err)
}
// constant-time compare
if !hmac.Equal(got, expected) {
return errors.New("verify: signature mismatch")
}
return nil
}

Comments are disabled for this gist.