# `Mailglass.Webhook.Providers.SendGrid`
[🔗](https://github.com/szTheory/mailglass/blob/v1.0.0/lib/mailglass/webhook/providers/sendgrid.ex#L1)

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
