Skip to content

feat: Go template support for custom webhook payloads #433

@almeidapaulopt

Description

@almeidapaulopt

Problem

Webhooks are already supported in TSDProxy — see internal/config/config.go for the WebhookConfig struct and internal/core/webhook for the sender implementation. Events are fired on proxy status changes (start, stop, error, etc.) via broadcastStatusEvents in internal/proxymanager/proxymanager.go.

However, webhook payloads are currently fixed-format. To integrate with Slack, Discord, PagerDuty, or custom systems, users need an intermediary webhook transformer. This adds operational complexity.

Proposed Solution

Add an optional template field to webhook configurations that uses Go text/template (or html/template for HTML-safe payloads) to render the HTTP request body. The template receives the webhook event data:

webhooks:
  - url: "https://hooks.slack.com/services/xxx/yyy/zzz"
    type: "slack"
    events: ["error", "stopped"]
    template: |
      {
        "text": "⚠️ Proxy {{.Name}} is now **{{.Status}}** (was {{.OldStatus}})",
        "attachments": [{
          "color": "danger",
          "fields": [
            {"title": "Proxy", "value": "{{.Name}}", "short": true},
            {"title": "Status", "value": "{{.Status}}", "short": true}
          ]
        }]
      }

  - url: "https://discord.com/api/webhooks/xxx/yyy"
    type: "discord"
    events: ["running", "error"]
    template: |
      {
        "embeds": [{
          "title": "Proxy {{.Name}}",
          "description": "Status changed to {{.Status}}",
          "color": {{if eq .Status "error"}}15158332{{else}}3066993{{end}}
        }]
      }

When no template is set, the existing default JSON payload is used (backward compatible).

Template Data Context

The template should have access to at minimum:

type WebhookEvent struct {
    Name      string   // proxy hostname
    Status    string   // current status (Running, Error, Stopped, etc.)
    OldStatus string   // previous status
    TargetID  string   // container ID / target ID
    Provider  string   // target provider name
    Image     string   // container image (from TargetImage)
    Timestamp time.Time
    URL       string   // proxy URL
}

Implementation Notes

  • The webhook sender lives at internal/core/webhook — parse and execute the template before the HTTP POST
  • Templates should be pre-parsed at config load time and cached to avoid re-parsing on every event
  • Template errors should be logged but not crash the event loop
  • Standard Go template functions are available by default; consider adding sprig functions for richer transformations
  • Default (no template) = current behavior, fully backward compatible

Alternatives

  • Template-less passthrough to n8n / Zapier / Pipedream (adds external dependency)
  • Fixed payload formats per type (less flexible, more maintenance)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions