SendGrid Event Webhook verifier + normalizer.
Verifier: ECDSA P-256 (prime256v1 / secp256r1) signature over
timestamp <> raw_body per the SendGrid Event Webhook security
docs. Public key is supplied as base64-encoded SubjectPublicKeyInfo
DER (NOT PEM — Pitfall 1; the SendGrid dashboard ships DER without
-----BEGIN PUBLIC KEY----- framing).
The verifier pattern-matches strictly on true from
:public_key.verify/4. false, {:error, _}, and DER-decode
exceptions all collapse to %SignatureError{type: :bad_signature}
per CONTEXT D-03 (closes the "wrong algo silently returns false"
footgun).
Replay protection: 300-second timestamp tolerance window
(Stripe / Svix / Standard Webhooks consensus; SendGrid does not
document one). Configurable via
config :mailglass, :sendgrid, timestamp_tolerance_seconds: N.
Provider identity lives in Event.metadata
The %Mailglass.Events.Event{} struct has no :provider column
(per V02 schema — provider identity lives on mailglass_webhook_events).
This module stashes "event" + "sg_message_id" in Event.metadata
with STRING keys (revision W9; JSONB roundtrip safety). Plan 06's
Ingest Multi reads these metadata keys to populate the
mailglass_webhook_events row's UNIQUE columns.
Normalizer: decodes the JSON array of events (1..128 per request);
maps each event string to the Anymail taxonomy verbatim. Unmapped
strings fall through to :unknown with Logger.warning per D-05.