Set Up Chat And Notification Webhooks¶
Use Hubuum's webhook sink to send selected events directly to Slack,
Mattermost, or Discord, or through a notification bridge such as Apprise.
The destination controls its message format; Hubuum supplies a JSON template,
secret lookup, delivery tracking, retries, and optional pacing.
| Destination | Start here | Payload | Successful acknowledgement |
|---|---|---|---|
| Slack | Create a Slack webhook | text |
HTTP 200 with ok |
| Mattermost | Create a Mattermost webhook | text |
HTTP 200 with ok |
| Discord | Discord recipe | content |
HTTP 200 with wait=true |
| Apprise API | Bridge recipe | title and body |
Match the deployed API; the recipe uses HTTP 200 |
These recipes use incoming webhooks. Bot installation, channel discovery, interactive actions, and OAuth token refresh belong in an integration service. For payload fields, response rules, and upgrade requirements, see the webhook reference.
Prerequisites¶
Use an administrator token to create sinks and send tests.
Collection subscriptions require ManageEventSubscription on that collection;
system subscriptions require an unscoped administrator.
Enable delivery in the environment of each hubuum-server process that should
run workers, then restart those processes:
Fan-out is enabled by default, but delivery workers default to zero. Setting these variables in an API client's terminal does not configure a running server. Ensure every delivery-worker process can resolve the same secret aliases.
Destinations must use HTTPS with a valid, trusted certificate. Hubuum refuses
redirects. For a Mattermost server or bridge on a private network, set
HUBUUM_REMOTE_CALL_ALLOW_PRIVATE_TARGETS=true on the worker processes and
restart them. This setting also permits private destinations for other outbound
remote calls; use it only where that deployment-wide access is intended.
Use the service's DNS name and final HTTPS endpoint, not a login page or an
HTTP URL that redirects to HTTPS.
Slack¶
- Create or open an app in Slack's app dashboard for the destination workspace.
- Enable Incoming Webhooks, then select Add New Webhook to Workspace.
- Select the channel and authorize the installation. Join a private channel before selecting it; follow any workspace approval requirements.
- Copy the generated URL, usually beginning
https://hooks.slack.com/services/. Store the entire URL as described below.
Slack chooses the channel, sender name, and icon from the webhook/app settings; a payload cannot override them. Create another webhook and Hubuum sink for a different channel. See Slack's incoming webhook guide.
The shared recipe below uses one-second spacing, consistent with Slack's
documented incoming-webhook rate. Other senders to the same channel still count
toward Slack's limits. HTTP 429 triggers a cooldown using Retry-After.
See Slack's rate limits.
Mattermost¶
- Open Product Menu > Integrations > Incoming Webhooks and add a webhook. If the option is unavailable, an administrator must enable incoming webhooks or grant access under System Console > Integrations > Integration Management.
- Choose a name and destination channel. Select Lock to this channel for a fixed destination, then save.
- Copy the complete generated URL, such as
https://mattermost.example.com/hooks/GENERATED_KEY.
The shared text recipe below works with Mattermost's incoming-webhook API.
Unlocked webhooks may accept a channel field in the rendered JSON, subject to
server policy and the creator's access. Username and icon overrides also depend
on server settings. These are payload fields, not Hubuum routing settings.
One-second pacing is a starting choice here; adjust it to your server's policy.
See Mattermost's incoming webhook guide.
Store The Destination Secret¶
For either service, use the alias ops_chat_webhook. Its value is the complete
webhook URL, including its secret path. Choose the mapping for your existing
secret source:
| Secret source | Where to put the complete webhook URL |
|---|---|
| Environment | HUBUUM_EVENT_SINK_SECRET_OPS_CHAT_WEBHOOK in each delivery worker's environment |
| Mounted files | event-sink/ops_chat_webhook relative to HUBUUM_SECRET_FILE_ROOT |
For example, with the default environment source, configure the worker's environment using the URL from the chosen service:
HUBUUM_EVENT_SINK_SECRET_OPS_CHAT_WEBHOOK='https://hooks.slack.com/services/REPLACE/WITH/REAL_VALUE'
Restart workers after changing environment-backed values. For file-backed secrets, mount a readable file containing only the URL, without a trailing newline, and use the file-source settings from the secret-source guide. To connect both services, use distinct aliases and create a sink for each.
Keep the URL out of subscription JSON and version control.
config.url_secret_ref selects the URL alias. The separate top-level
secret_ref supplies an HTTP bearer token and is unnecessary for Slack,
Mattermost, and Discord incoming webhooks.
Create A Sink¶
Save the following as chat-sink.json. It works for Slack and Mattermost:
{
"name": "ops-chat",
"kind": "webhook",
"config": {
"url_secret_ref": "ops_chat_webhook",
"body_template": "{\"text\": {{ (test_marker ~ 'Hubuum: ' ~ summary) | tojson }}}",
"response": {
"success_statuses": [200],
"rate_limit": true,
"retry_statuses": [408, 500, 502, 503, 504],
"body": { "kind": "text_equals", "value": "ok" }
}
},
"delivery_policy": { "min_interval_ms": 1000 },
"enabled": true
}
With HUBUUM_TOKEN containing your administrator token, create the sink:
export HUBUUM_URL='https://hubuum.example.com'
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $HUBUUM_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @chat-sink.json \
"$HUBUUM_URL/api/v1/event-sinks"
Record the response's numeric id. The following examples use 3; replace
it with your sink ID. For several subscriptions going to the same destination,
reuse this sink so they share its pacing. Separate sinks have separate schedules.
The template uses tojson to escape dynamic JSON values and displays [TEST]
for test deliveries. Rich messages can add Slack blocks or Mattermost fields
inside the JSON template. Follow the destination's payload and length limits;
Hubuum checks valid JSON and rendering budgets, not each provider's schema.
The response policy requires HTTP 200 and the trimmed body ok. It retries the
listed transient statuses, defers HTTP 429 without spending a failure attempt,
and makes other HTTP failures permanent. This avoids repeatedly sending an
invalid payload or using a revoked webhook URL. Transport errors still retry.
Subscribe To Events¶
For object changes, save this as chat-subscription.json:
{
"sink_id": 3,
"name": "object-changes-to-chat",
"description": "Notify operations of object changes in this collection",
"entity_types": ["object"],
"actions": ["created", "updated"],
"routing": {},
"enabled": true
}
Replace 12 with a collection you manage:
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $HUBUUM_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @chat-subscription.json \
"$HUBUUM_URL/api/v1/collections/12/event-subscriptions"
Record the subscription's id; the test below uses 7. Leave routing empty
when url_secret_ref supplies the destination. A routing.url override is
rejected. New matching events fan out only while both sink and subscription
are enabled; creating a subscription does not replay the audit history.
For failed backups, create a separate system subscription by posting this
body to POST /api/v1/system-event-subscriptions:
{
"sink_id": 3,
"name": "failed-backups-to-chat",
"description": "Notify operations when a backup task fails",
"entity_types": ["task"],
"actions": ["failed"],
"filter": { "task_kinds": ["backup"] },
"routing": {},
"enabled": true
}
System subscriptions match events with neither a direct nor a related collection. Use Prometheus alerts for thresholds such as queue age or database pressure; event subscriptions match emitted facts.
Preview, Test, And Check Delivery¶
For the collection example, find a saved event in that collection:
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $HUBUUM_TOKEN" \
"$HUBUUM_URL/api/v1/events?collection_id=12&entity_type=object&sort=-occurred_at&limit=1"
If none exists, create or update an object there and query again. Copy the
event's UUID event_id, not its integer id. For a system subscription,
select a collection-less event instead, such as an existing backup task event.
Save chat-test.json, replacing both example values with saved records:
Preview the rendered payload:
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $HUBUUM_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @chat-test.json \
"$HUBUUM_URL/api/v1/event-sinks/3/preview"
Preview performs no secret lookup or network request, so success verifies the
template and event scope but not connectivity or credentials. Check the JSON
and [TEST] label, then queue a real message:
curl --fail-with-body --silent --show-error --include \
-H "Authorization: Bearer $HUBUUM_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @chat-test.json \
"$HUBUUM_URL/api/v1/event-sinks/3/test"
HTTP 202 Accepted means queued, not delivered. The response headers include a
Location such as /api/v1/event-deliveries/42. Use its actual path to inspect
the delivery:
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $HUBUUM_TOKEN" \
"$HUBUUM_URL/api/v1/event-deliveries/42"
Check until the delivery is succeeded, failed, or dead. A failed delivery
may still be retryable.
Tests bypass subscription filters and enabled flags, while retaining scope
checks and pacing. They do not require a new failed backup. Verify normal
delivery separately by making a matching change after enabling the subscription.
See delivery semantics for inspection and retry
endpoints. Sink/subscription updates use the current ETag with If-Match.
Discord¶
Create a webhook for a regular text channel through Server Settings >
Integrations > Webhooks and copy its URL. See
Discord's setup guide.
Store the complete URL under ops_discord_webhook, appending ?wait=true
(&wait=true if it already has a query). Waiting makes Discord confirm message
creation before responding. Use this sink with the subscription/test steps above:
{
"name": "ops-discord",
"kind": "webhook",
"config": {
"url_secret_ref": "ops_discord_webhook",
"body_template": "{\"content\": {{ (test_marker ~ 'Hubuum: ' ~ summary)[:1900] | tojson }}, \"allowed_mentions\": {\"parse\": []}}",
"response": {
"success_statuses": [200],
"rate_limit": true,
"retry_statuses": [408, 500, 502, 503, 504]
}
},
"delivery_policy": { "min_interval_ms": 1000 },
"enabled": true
}
The template limits content below Discord's 2,000-character maximum and disables
automatic mentions. Discord returns a message object, so omit the Slack/Mattermost
text_equals rule. Forum and media channels need additional thread settings;
see the execute-webhook contract.
Apprise And Other Bridges¶
An external bridge can manage multiple chat and notification services while
Hubuum maintains one webhook contract. With Apprise API, save destinations in a
stateful configuration, then use its /notify/{KEY} endpoint. The example below
assumes https://notify.example.com/notify/hubuum is exposed through an HTTPS
gateway accepting a bearer token; the gateway handles the backend's required
authentication. Store that full URL as ops_apprise_url and the gateway token
as apprise_gateway_token in Hubuum's secret source.
{
"name": "ops-notification-bridge",
"kind": "webhook",
"config": {
"url_secret_ref": "ops_apprise_url",
"body_template": "{\"title\": \"Hubuum\", \"body\": {{ (test_marker ~ summary) | tojson }}}",
"response": {
"success_statuses": [200],
"rate_limit": true,
"retry_statuses": [408, 500, 502, 503, 504]
}
},
"secret_ref": "apprise_gateway_token",
"enabled": true
}
Apprise's own authentication is deployment-dependent; Hubuum's secret_ref
always sends Bearer, not Basic authentication. Do not embed credentials in
the URL. Match the response policy to your deployed bridge's synchronous or
queued acknowledgement contract; acceptance by a bridge may not prove delivery
to every downstream service. See the Apprise API configuration and notification reference.
Other services fit directly when they accept HTTPS JSON POST, can authenticate
using a secret URL or bearer token, and have an acknowledgement expressible with
Hubuum's response rules. Use a bridge
for token refresh, signing, form uploads, or provider-specific stateful workflows.
How These Examples Are Tested¶
The test suite reads the JSON examples directly from this page. API tests create
the sinks and both subscription scopes, preview a saved event, and queue a test
delivery. Transport tests render normal and test messages with the production
template worker, then send them to disposable HTTPS fixtures with a verified
local certificate authority. The fixtures check Slack/Mattermost text, Discord
content, wait=true and disabled mentions, and the Apprise gateway's bearer
authentication. They exercise successful acknowledgements, rejected payloads,
rate limits, and transient errors using these response policies.
Run the checks from a repository checkout with the prerequisites in the development guide:
These are provider-contract fixtures, not running Slack, Mattermost, Discord,
or Apprise deployments. They do not verify workspace permissions, a real
webhook's credentials, or downstream bridge delivery. Before relying on a new
destination, use preview and test delivery
with a dedicated channel, confirm succeeded, and check that the [TEST]
message appears there. For a bridge, check each intended downstream service.
Troubleshooting¶
| Symptom | What to check |
|---|---|
| Preview works, test fails | Preview does not check secrets or networking. Check the worker's secret source, full URL, DNS, HTTPS certificate, and private-target policy. |
| Test remains pending | Confirm delivery workers are running; inspect deferred_reason and next_attempt_at for configured pacing or HTTP cooldowns. |
| No delivery row for a new event | Confirm fan-out workers, both enabled flags, scope, entity/action selection, and filters. Inspect /api/v1/event-deliveries/health. |
| Delivery becomes dead immediately | Check for a revoked URL, wrong channel access, invalid payload, provider size limit, or acknowledgement mismatch. This recipe treats unlisted HTTP errors as permanent. |
| Repeated cooldowns | Reduce traffic or increase min_interval_ms. Several sinks or applications can share one provider limit. |
| Duplicate messages | Delivery is at least once. A lost acknowledgement can cause another post; chat services need not deduplicate Hubuum's event or idempotency headers. |
Delivery errors omit secret URLs and provider response bodies. Use the provider
or bridge's own diagnostics when a sanitized failure needs more detail. After
fixing the cause, explicitly retry a dead delivery through the administrator
retry endpoint. Adding event_id to the message can help identify duplicates.