knadh/listmonk · error
error asn1 unmarshal of signature: %v
Error message
error asn1 unmarshal of signature: %v
What it means
SendGrid signs webhook payloads with ECDSA; the base64-decoded signature must be a DER/ASN.1-encoded ECDSA signature (R,S). verifyNotif asn1.Unmarshals the decoded bytes into an {R,S} struct, and returns 'error asn1 unmarshal of signature' when the bytes are not a valid ASN.1 ECDSA-Sig-Value structure.
Source
Thrown at internal/bounce/webhooks/sendgrid.go:102
}
return out, nil
}
// verifyNotif verifies the signature on a notification payload.
func (s *Sendgrid) verifyNotif(sig, timestamp string, b []byte) error {
sigB, err := base64.StdEncoding.DecodeString(sig)
if err != nil {
return err
}
ecdsaSig := struct {
R *big.Int
S *big.Int
}{}
if _, err := asn1.Unmarshal(sigB, &ecdsaSig); err != nil {
return fmt.Errorf("error asn1 unmarshal of signature: %v", err)
}
h := sha256.New()
h.Write([]byte(timestamp))
h.Write(b)
hash := h.Sum(nil)
if !ecdsa.Verify(s.pubKey, hash, ecdsaSig.R, ecdsaSig.S) {
return errors.New("invalid signature")
}
return nil
}
View on GitHub (pinned to 670c01717d)
Solutions
- Confirm SendGrid Event Webhook signature verification is enabled (ECDSA v1) so the header contains a base64 ASN.1 signature
- Verify the signature header is passed through unmodified (base64, no truncation or added whitespace) by proxies
- Regenerate/copy the exact signature from the actual request headers when testing manually
- Ensure the ECDSA public key configured in NewSendgrid matches the one SendGrid currently signs with
Example fix
// before (test header with raw hex r||s) sig := hex.EncodeToString(sigRS) // after sig := base64.StdEncoding.EncodeToString(asn1EncodedEcdsaSig) // DER ECDSA-Sig-Value
Defensive patterns
Strategy: validation
Validate before calling
func validEcdsaSigHeader(sig string) bool {
sigB, err := base64.StdEncoding.DecodeString(sig)
if err != nil { return false }
var ecdsaSig struct {
R *big.Int
S *big.Int
}
_, err = asn1.Unmarshal(sigB, &ecdsaSig)
return err == nil && ecdsaSig.R != nil && ecdsaSig.S != nil
}
// return 400 if !validEcdsaSigHeader(sigHeader) Try / catch
bounces, err := handler.ProcessBounce(sig, ts, body)
if err != nil {
if strings.Contains(err.Error(), "error asn1 unmarshal of signature") {
log.Printf("bad ECDSA signature header (len=%d)", len(sig))
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
http.Error(w, "webhook error", http.StatusInternalServerError)
} Prevention
- Ensure SendGrid signature verification (ECDSA) is enabled so signatures are ASN.1/DER, not raw r||s
- Forward the signature header verbatim; base64 content must not be altered or truncated
- Keep the SendGrid verification public key in sync with SendGrid's current signing cert
- Never mix HMAC and ECDSA signature schemes in tests
When it happens
Trigger: ProcessBounce receives an X-Webhook-Signature header whose base64-decoded bytes are not DER-encoded ECDSA (R,S): raw r||s concatenation, hex instead of base64, truncated signature, empty header, or a signature produced by a different scheme (e.g. HMAC).
Common situations: SendGrid webhook verification settings changed between signature schemes/versions; copying the verification key or signature incorrectly when testing; a proxy truncating the header; using the wrong SendGrid public key so the test signature was generated under a different format; SendGrid rotating their signing cert.
Related errors
- invalid timestamp in signature: %v
- invalid signature format
- error unmarshalling Sendgrid notification: %v
- error unmarshalling postmark notification: %v
- unsupported bounce type: %v
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/c153d2b98906ec7d.
Report an issue: GitHub.