knadh/listmonk · error

webhook key is not configured

Error message

webhook key is not configured

What it means

Lettermint's ProcessBounce requires an HMAC key (set via NewLettermint) to verify webhook signatures. This error is thrown immediately when the handler was constructed with a nil or empty key, before any signature parsing occurs.

Source

Thrown at internal/bounce/webhooks/lettermint.go:47

		Metadata json.RawMessage `json:"metadata"`
		Tag      string          `json:"tag"`
	} `json:"data"`
}

// Lettermint handles bounce webhook notifications from Lettermint.
type Lettermint struct {
	hmacKey []byte
}

// NewLettermint returns a new Lettermint webhook handler.
func NewLettermint(key []byte) *Lettermint {
	return &Lettermint{hmacKey: key}
}

// ProcessBounce processes an incoming Lettermint webhook payload and returns a bounce object.
func (l *Lettermint) ProcessBounce(sig string, body []byte) ([]models.Bounce, error) {
	if len(l.hmacKey) == 0 {
		return nil, fmt.Errorf("webhook key is not configured")
	}

	// Parse the signature header: t={timestamp},v1={hex_signature}.
	ts, sigHex, err := parseLettermintSignature(sig)
	if err != nil {
		return nil, err
	}

	// Verify timestamp tolerance (300 seconds).
	if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
		return nil, fmt.Errorf("signature timestamp expired")
	}

	// Decode the hex signature from the header.
	sigB, err := hex.DecodeString(strings.TrimSpace(sigHex))
	if err != nil {
		return nil, fmt.Errorf("invalid signature encoding: %v", err)
	}

View on GitHub (pinned to 670c01717d)

Solutions

  1. Set the Lettermint webhook signing key in your configuration/environment before starting the server.
  2. Guard at startup: fail fast if the key is empty when webhook handling is enabled, rather than failing per-request.
  3. Check the config-loading path for silent empty defaults (e.g. viper/env readers returning "" when a var is missing).
  4. Verify the correct secret is wired: NewLettermint must receive the same key Lettermint uses to sign webhooks.

Example fix

// before: silently empty key
lm := webhooks.NewLettermint([]byte(cfg.WebhookKey)) // "" if unset

// after: fail fast at startup
if cfg.WebhookKey == "" {
    log.Fatal("LETTERMINT_WEBHOOK_KEY is required")
}
lm := webhooks.NewLettermint([]byte(cfg.WebhookKey))
Defensive patterns

Strategy: validation

Validate before calling

func mustNewLettermint(key []byte) *webhooks.Lettermint {
    if len(key) == 0 {
        panic("lettermint webhook key is required")
    }
    return webhooks.NewLettermint(key)
}

Try / catch

bounces, err := lm.ProcessBounce(sig, body)
if err != nil {
    if err.Error() == "webhook key is not configured" {
        // server misconfiguration: alert ops, return 500
        log.Printf("configuration error: %v", err)
        http.Error(w, "server misconfiguration", http.StatusInternalServerError)
        return
    }
    http.Error(w, "bad request", http.StatusBadRequest)
}

Prevention

When it happens

Trigger: Calling NewLettermint(nil), NewLettermint([]byte{}), or NewLettermint(key) where key comes from an empty/unset config value or environment variable, then receiving any webhook call to ProcessBounce.

Common situations: The Lettermint webhook signing key env var not set in the deployment; config loading silently returning an empty string on parse failure; wiring up the handler in a dev environment without credentials; a refactor passing the wrong config field (empty by default).

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01). Data as JSON: /api/errors/2caf46f99d3093a6. Report an issue: GitHub.