Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/slack"
Posts text to a Slack Incoming Webhook.
c, err := slack.New(webhookURL, "my-bot", ":rocket:", "", "#alerts")
err = c.Send(ctx, "deploy finished", "", "", "", "")
The webhook URL is a credential, and it appears in error messages
This is the part that separates the package from an inline http.Post.
A Slack Incoming Webhook URL contains the secret. Anyone holding https://hooks.slack.com/services/T00.../B00.../XXXX can post to that channel; there is no separate token.
Go’s net/http includes the request URL in transport error text. A DNS failure, a timeout, or a refused connection produces an error whose Error() string contains the full URL. Log that error and the credential is now in your log aggregation, your error tracker, and anywhere those get shipped.
The client wraps its errors so the configured address is replaced with REDACTED in the message, while Unwrap still returns the original error, so errors.Is and errors.As keep working:
type redactedError struct {
msg string // address replaced with REDACTED
err error // original, reachable via Unwrap
}
The same guard is in sleuth, which has the same exposure.
Per-message identity, with client defaults
New takes the default username, icon emoji, icon URL and channel. Send takes the same four again, and an empty value falls back to the client default:
c.Send(ctx, "routine", "", "", "", "") // all defaults
c.Send(ctx, "urgent", "alertbot", ":fire:", "", "#oncall") // overridden
One client therefore serves several notification kinds without constructing one per channel, which matters because the webhook URL is bound to a workspace rather than to a channel and the channel override is what routes the message.
Icon emoji and icon URL are separate parameters because Slack treats them as alternatives; supplying both lets Slack pick.
HealthCheck probes somewhere else
HealthCheck does not post to the webhook. It checks the Slack Status API by default, configurable through WithPingURL, and uses its own timeout through WithPingTimeout.
That separation is deliberate. A health check that exercised the real webhook would post a message to a human-visible channel every time a readiness probe ran. Probing a status endpoint answers whether Slack is reachable without producing traffic anyone has to read.
It also means a passing health check does not prove the webhook itself is valid. A revoked webhook fails at Send, not at HealthCheck.
Options
| Option | Effect |
|---|---|
WithTimeout | per-message request timeout |
WithPingTimeout | health-check timeout, separate from the above |
WithPingURL | where HealthCheck probes |
WithHTTPClient | inject a transport, or a mock |
WithRetryAttempts | retries on a failed send |
WithHTTPClient takes an HTTPClient interface rather than *http.Client, so a test can supply a mock and a service can supply an instrumented client from httpclient.
Notifications are not a delivery guarantee
Send is synchronous and returns an error when the post fails after its retries. Slack rate-limits Incoming Webhooks, and a burst of alerts can exhaust the retry budget.
Treat a notification path as best-effort and keep the authoritative record in your logs or metrics. A failed Send should not fail the operation that triggered it.
Example
c, err := slack.New("https://hooks.slack.com/services/...", "bot", ":robot_face:", "", "#general")
if err != nil {
log.Fatal(err)
}
err = c.Send(context.Background(), "hello", "", "", "", "")
Full source is in example_slack_test.go. More runnable examples are on pkg.go.dev.
Dependencies
This package reaches no external module: it uses only the Go standard library.
Links
- Reference documentation
- Source code
- Instrumented outbound transport: httpclient
- Keeping secrets out of logs generally: redact
- All nurago packages