From 8e0d2ee1d62874b3eeb59e0cee1ac96f72a04171 Mon Sep 17 00:00:00 2001 From: Lennart Fleischmann <67686424+lfleischmann@users.noreply.github.com> Date: Thu, 12 Mar 2026 11:31:19 +0100 Subject: [PATCH] fix: add timeout to webhook trigger requests (#2488) --- backend/webhooks/webhook.go | 17 ++++++++++++++++- backend/webhooks/webhook_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/backend/webhooks/webhook.go b/backend/webhooks/webhook.go index ec7e24a9..34195c63 100644 --- a/backend/webhooks/webhook.go +++ b/backend/webhooks/webhook.go @@ -30,6 +30,7 @@ type BaseWebhook struct { Logger echo.Logger Callback string Events events.Events + Timeout time.Duration } func (bh *BaseWebhook) HasEvent(evt events.Event) bool { @@ -57,13 +58,27 @@ func (bh *BaseWebhook) Trigger(data JobData) error { } request.Header.Set("Content-Type", "application/json") - client := http.Client{} + timeout := bh.Timeout + if timeout == 0 { + timeout = 10 * time.Second + } + + client := http.Client{ + Timeout: timeout, + } + response, err := client.Do(request) if err != nil { bh.Logger.Error(fmt.Errorf("unable to execute webhook request: %w", err)) return err } + defer func() { + if err := response.Body.Close(); err != nil { + bh.Logger.Error(fmt.Errorf("failed to close webhook response body: %w", err)) + } + }() + if response.StatusCode >= http.StatusBadRequest { err := fmt.Errorf("request failed due to status code: %d", response.StatusCode) bh.Logger.Error(err) diff --git a/backend/webhooks/webhook_test.go b/backend/webhooks/webhook_test.go index c922531c..324b2f28 100644 --- a/backend/webhooks/webhook_test.go +++ b/backend/webhooks/webhook_test.go @@ -4,6 +4,7 @@ import ( "github.com/labstack/gommon/log" "github.com/stretchr/testify/require" "github.com/teamhanko/hanko/backend/v2/webhooks/events" + "net" "net/http" "net/http/httptest" "testing" @@ -145,3 +146,31 @@ func TestBaseWebhook_TriggerWithBadServer(t *testing.T) { require.Error(t, err) require.ErrorContains(t, err, "EOF") } + +func TestBaseWebhook_TriggerTimeout(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(100 * time.Millisecond) + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + baseHook := BaseWebhook{ + Logger: log.New("test"), + Callback: server.URL, + Events: events.Events{events.UserCreate}, + Timeout: 20 * time.Millisecond, + } + + data := JobData{ + Token: "test-token", + Event: "user", + } + + err := baseHook.Trigger(data) + + var netErr net.Error + + require.Error(t, err) + require.ErrorAs(t, err, &netErr) + require.True(t, netErr.Timeout()) +}