Teams that self-host their chat usually self-host for a reason: data stays on their own servers, the tool doesn't disappear when a vendor pivots, and nobody pays per seat. The trade-off is that every SaaS integration list says "Slack, Teams, Discord" and stops. CronAlert's list stops there too. But Mattermost and Rocket.Chat both made a decision years ago that makes this a non-problem: their incoming webhooks accept Slack's payload format. Point CronAlert's Slack channel at one of their webhook URLs and downtime alerts arrive in your self-hosted chat with no relay, no bot, and no code.
This post walks through the setup for each tool, shows exactly what arrives, covers the two things that trip people up (an internal-only chat server and a self-signed certificate), and explains why the chat server should not be the only place an alert goes.
Why the Slack channel type works
When a monitor goes down, CronAlert posts one JSON document to your Slack webhook URL. That document has two parts: a blocks array, which Slack renders as a card with a colored header and a grid of fields, and a top-level text string carrying the same information as plain text. Slack uses text as the notification preview. Mattermost and Rocket.Chat don't know what blocks is, ignore it, and render text, which both of them require in a Slack-format payload. So the same request satisfies all three.
Two consequences worth knowing up front. First, what you get in Mattermost or Rocket.Chat is the plain-text version of the alert, not the Slack card; it has every field, just without the layout. Second, nothing about the URL is validated as Slack-specific. CronAlert accepts any public https:// webhook URL in the Slack channel, so a Mattermost URL at https://chat.example.com/hooks/… saves and works the same way a hooks.slack.com URL does.
Mattermost
Create the incoming webhook
- In Mattermost, open the product menu (the grid icon at the top left) and choose Integrations, then Incoming Webhooks, then Add Incoming Webhook. If you don't see Integrations, an admin needs to turn on incoming webhooks under System Console → Integrations → Integration Management, or grant your role permission to manage them.
- Give it a title such as "CronAlert" and pick the channel alerts should post to. Tick Lock to this channel so a payload can't redirect the message elsewhere.
- Save and copy the URL. It looks like
https://chat.example.com/hooks/abc123…on a self-hosted server, orhttps://yourteam.cloud.mattermost.com/hooks/…on Mattermost Cloud.
Add it to CronAlert
- In CronAlert, go to Alert Channels and create a new channel of type Slack.
- Paste the Mattermost webhook URL into the webhook field. Leave the optional channel field empty; the destination is whichever channel you created the webhook for.
- Save, then use the channel's Send test alert button. A test message should appear in Mattermost within a second or two.
What arrives
A down alert posts as a short block of text. Mattermost renders it as Markdown, so the URL becomes a link:
[DOWN] Checkout API
URL: https://api.example.com/health
Status Code: 503
Error: Expected status 200, got 503
Region: us-east
Time: 2026-09-25T03:00:12.000Z When the monitor recovers, a second message closes the loop with the outage duration:
[RECOVERED] Checkout API
URL: https://api.example.com/health
Downtime: 7m 30s
Time: 2026-09-25T03:07:30.000Z Status code and error lines appear only when the check produced them; a timeout has no status code, a TCP port check has an error but no HTTP status, and multi-region monitors list the failing regions instead of a single one. If you want alerts from different environments in different channels, create one webhook per channel in Mattermost and one Slack channel per webhook in CronAlert.
Rocket.Chat
Create the incoming webhook
- Open Administration → Workspace → Integrations, click New, and choose Incoming Webhook.
- Turn Enabled on, name it "CronAlert", set Post to Channel to your alerts channel (for example
#ops-alerts), and set Post as to a user; a dedicated bot user keeps alerts visually distinct from people. - Leave Script Enabled off for now and save. Rocket.Chat shows a webhook URL of the form
https://chat.example.com/hooks/<id>/<token>. Copy it.
Then add it to CronAlert exactly as above: new Slack channel, paste the URL, save, send a test. The message that arrives is the same plain text shown in the Mattermost section.
Optional: add a color bar with a script
Rocket.Chat incoming webhooks can run a small JavaScript transform on each request before posting. That's enough to turn the plain text into an attachment with a red or green bar, which makes a wall of alerts scannable. Back in the integration, turn Script Enabled on and paste:
class Script {
process_incoming_request({ request }) {
const text = request.content.text || "";
const down = text.startsWith("[DOWN]");
return {
content: {
text: "",
attachments: [{ color: down ? "#dc2626" : "#16a34a", text }]
}
};
}
} Save and send another test alert. The message now arrives as an attachment with a colored edge, red for down and green for recovered, and the text inside is unchanged. Everything else in this post works without the script; it's purely cosmetic.
Zulip and other Slack-compatible tools
The same trick works anywhere that advertises a Slack-compatible incoming webhook. Zulip, for instance, has a "Slack-compatible webhook" integration that produces a URL you can paste into a CronAlert Slack channel, and it reads the same text field. The test is always the same: paste the URL, click Send test alert, and see whether the message lands. If a tool needs a payload shape that isn't Slack-like at all, use CronAlert's generic webhook channel instead and reshape the JSON in a small relay; that post has the pattern and several worked recipes.
Reaching a chat server that lives on your network
This is where most attempts stall. CronAlert's alert dispatcher runs on Cloudflare's network. When it posts to your webhook URL it needs three things to be true:
- The hostname resolves publicly and isn't private. CronAlert rejects webhook URLs on private or internal addresses (10.x, 192.168.x,
.local, and so on) when you save the channel, because it could never reach them. A Tailscale or WireGuard address doesn't help either; the dispatcher isn't on your VPN. - HTTPS with a certificate a browser would trust. A self-signed certificate on Mattermost or Rocket.Chat fails the TLS handshake and the alert is dropped with a dispatch error. Put a Let's Encrypt certificate on the hostname, and monitor its renewal so the alert path doesn't silently expire.
- The
/hooks/path answers from the internet. It doesn't have to be the whole server.
That last point is the usual compromise for a chat server you deliberately keep internal. In your reverse proxy, publish a hostname that forwards only the webhook path (in nginx terms, a location /hooks/ block proxying to the chat server and nothing else), or expose that path with a Cloudflare Tunnel public hostname. Logins, file uploads, and the rest of the app stay on the LAN; the webhook receiver, which authenticates each request by the secret in its URL, is the only thing reachable from outside. Treat that URL like a password: anyone who has it can post to the channel.
Don't alert only into the server that might be down
Self-hosted chat has a failure mode hosted chat doesn't: it often shares a host, a Docker network, a reverse proxy, or a Postgres instance with the things you're monitoring. The outage that takes your app down can take Mattermost down with it, and then the alert is posted into a void. Mattermost and Rocket.Chat both also have their own scheduled jobs, disk, and certificates to fail on, independent of anything you monitor.
Two cheap mitigations. Add a second channel that doesn't depend on your infrastructure: email, push to your phone, or Telegram. Alert channels in CronAlert are team-wide, so every enabled channel fires for every incident and there's nothing to assign per monitor. And monitor the chat server itself: an HTTPS check on its public hostname (Mattermost answers on /api/v4/system/ping, Rocket.Chat on /api/info) tells you the alert path is broken before you need it. The self-hosted stack guide covers the rest of that host.
If you want the rich card after all
Plain text is enough for most on-call channels, but if you want Mattermost message attachments with fields, or want to route different monitors to different channels from one CronAlert channel, use the generic webhook channel and a small Cloudflare Worker as a relay. CronAlert posts a signed JSON event (monitor.down or monitor.recovered, with monitor, incident, and check details) to the Worker; the Worker verifies the signature, builds whatever shape your chat tool prefers, and posts it on. The webhook integrations post walks through that relay pattern step by step, and it's the same code whether the destination is Mattermost, Rocket.Chat, Zulip, or a ticketing tool.
Frequently asked questions
Is there a native Mattermost or Rocket.Chat channel?
No, and you don't need one. Both accept Slack-format webhooks, so the Slack channel type delivers to them directly. It's available on every plan, including free.
What does the message look like?
Plain text: a down or recovered line with the monitor name, the URL, status code and error or downtime duration, region, and timestamp. Slack's card layout doesn't render in either tool.
Can CronAlert reach my internal chat server?
Only via a public HTTPS hostname with a trusted certificate. Expose just the /hooks/ path through your reverse proxy or a Cloudflare Tunnel.
Why did my test alert fail with a certificate error?
The chat server is serving a self-signed or expired certificate. Put a Let's Encrypt certificate on the hostname CronAlert posts to.
Can I get alerts in Mattermost and by email?
Yes, and you should. Channels are team-wide; every enabled one fires for every incident.
Two webhooks and you're done
Self-hosting your chat shouldn't mean building an integration every time a tool leaves you off its list. One incoming webhook in Mattermost or Rocket.Chat, one Slack channel in CronAlert pointed at it, one test alert to confirm the path, and a second channel that doesn't live on your servers. Create a free account, add the channel, and send the test. Related reading: Slack alerts for the original version of this setup, Microsoft Teams alerts if part of your organization lives there, webhook integrations for everything without a Slack-compatible endpoint, and how to test your alerts before an outage does it for you.