Webhook (Generic)
Fire a signed HTTP POST at any URL. Glue for custom integrations.
The webhook channel POSTs a JSON payload to a URL you configure. Use it to:
- Push notifications into a system this library doesn't have a native driver for.
- Trigger internal services (your analytics pipeline, a Zapier/Make/n8n flow, another microservice).
- Prototype a new driver idea before writing a proper one.
Setup
import "github.com/gopackx/go-notification/channel/webhook"
notifier.RegisterChannel(webhook.New(webhook.Config{
DefaultURL: os.Getenv("OPS_WEBHOOK_URL"),
SigningSecret: os.Getenv("OPS_WEBHOOK_SECRET"), // used for HMAC signing
DefaultHeaders: map[string]string{
"X-Source": "go-notification",
},
}))The driver registers as "webhook". To run more than one webhook target, give each a distinct Config.Name and reference those names in Via.
Sending
The body is exactly what you set with SetJSON (or SetForm/SetRaw) — there is no automatic envelope. Build the payload shape your receiver expects:
func (n OrderShipped) Via(u notification.Notifiable) []string {
return []string{"webhook"}
}
func (n OrderShipped) ToWebhook(u notification.Notifiable) *webhook.Message {
return webhook.NewMessage().
SetJSON(map[string]any{
"event": "order.shipped",
"order_id": n.OrderID,
"user_id": u.GetID(),
"shipped_at": time.Now().UTC(),
})
}Per-message overrides: SetURL(url), SetMethod("PUT"), AddHeader(k, v). Use SetForm(map[string]string{...}) for form-encoded bodies or SetRaw([]byte{...}) for raw bytes.
Configuration reference
| Field | Type | Required | Description |
|---|---|---|---|
DefaultURL | string | no* | URL used when a message sets no URL. *Required unless every message calls SetURL. |
DefaultHeaders | map[string]string | no | Headers added to every request (merged with per-message). |
SigningSecret | string | no | HMAC-SHA256 secret. If set, adds the X-Signature header. |
Timeout | time.Duration | no | HTTP timeout per send. Default: 30s. |
Name | string | no | Override the channel name. Default: "webhook". |
The HTTP method defaults to POST and is set per message via SetMethod.
Request format
The request body is your SetJSON payload verbatim. When SigningSecret is set and the body is non-empty, the driver adds an X-Signature header:
POST /your-webhook HTTP/1.1
Content-Type: application/json
X-Signature: sha256=<hex-hmac-sha256-of-raw-body>
X-Source: go-notification
{ "event": "order.shipped", "order_id": "A-1024", "user_id": "123", "shipped_at": "2026-04-18T10:15:03Z" }Verifying signatures on the receiver
func verify(secret, body []byte, signatureHeader string) bool {
mac := hmac.New(sha256.New, secret)
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signatureHeader))
}Always compare with hmac.Equal, never == — the former is constant-time.
Non-2xx responses
The driver treats any response outside 2xx as a failed send and retries according to the notifier's retry policy. If your downstream rejects duplicates, use an idempotency key in the payload and de-dupe on the receiver.
Troubleshooting
- Timeouts under load — increase
Timeout, or make the receiver respond202 Acceptedfast and do work async. - Receiver sees body-less request — check that your reverse proxy (Nginx, Cloudflare) isn't stripping POST bodies on retry.
- Signature mismatch — usually a whitespace/encoding difference on the receiver. Compare hashes with the exact raw body bytes, not a re-serialized version.