package handler import ( "crypto/hmac" "crypto/sha256" "crypto/subtle" "encoding/hex" "encoding/json" "io" "net/http" fwlogger "github.com/yourorg/go-dw-platform/framework/logger" "github.com/yourorg/go-dw-platform/services/omnix-broadcast/dto" "github.com/yourorg/go-dw-platform/services/omnix-broadcast/service" ) const signatureHeader = "X-Sopiga-Signature" // WebhookHandler receives delivery-status callbacks pushed by Omnix, // as an alternative to polling. Payload shape confirmed by the Omnix team // (see dto.DeliveryStatusWebhook); the signature scheme is still our own // proposal pending their confirmation. type WebhookHandler struct { service *service.Service logger *fwlogger.Logger secret string } func NewWebhookHandler(svc *service.Service, log *fwlogger.Logger, secret string) *WebhookHandler { return &WebhookHandler{service: svc, logger: log, secret: secret} } func (h *WebhookHandler) HandleDeliveryStatus(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } body, err := io.ReadAll(r.Body) if err != nil { h.logger.Error("webhook: read body failed", "error", err) w.WriteHeader(http.StatusBadRequest) return } defer r.Body.Close() if h.secret != "" && !h.verifySignature(body, r.Header.Get(signatureHeader)) { h.logger.Error("webhook: signature verification failed") w.WriteHeader(http.StatusUnauthorized) return } var payload dto.DeliveryStatusWebhook if err := json.Unmarshal(body, &payload); err != nil { h.logger.Error("webhook: invalid payload", "error", err) w.WriteHeader(http.StatusBadRequest) return } if err := h.service.SyncDeliveryFromWebhook(r.Context(), payload); err != nil { h.logger.Error("webhook: sync delivery failed", "recipient_detail_id", payload.RecipientDetailID, "error", err) w.WriteHeader(http.StatusInternalServerError) return } h.logger.Info("webhook: delivery status applied", "recipient_detail_id", payload.RecipientDetailID, "status", payload.Status, "gateway", payload.Gateway) w.WriteHeader(http.StatusOK) } func (h *WebhookHandler) verifySignature(body []byte, signature string) bool { mac := hmac.New(sha256.New, []byte(h.secret)) mac.Write(body) expected := hex.EncodeToString(mac.Sum(nil)) return subtle.ConstantTimeCompare([]byte(expected), []byte(signature)) == 1 }