Alert webhooks
Send alerts to Slack, Discord, Google Chat, or your own endpoint, with the exact payloads.
Every alert rule can post to a webhook URL. Paste a chat webhook and you get alerts in the channel your team already reads; paste your own endpoint and you get a structured JSON payload you can do anything with.
Provider-aware payloads
LaunchPulse looks at the hostname of your webhook URL and shapes the request body to match the receiver. You don’t configure this — it just happens.
Google Chat (chat.googleapis.com) and Slack (hooks.slack.com)
{ "text": "🚨 Signup spike — MyApp: account_created = 812 (≥ 500) in 1h" }
Discord (discord.com, discordapp.com)
{ "content": "🚨 Signup spike — MyApp: account_created = 812 (≥ 500) in 1h" }
Everything else — the generic payload
{
"type": "alert.fired",
"project": "MyApp",
"rule": "Signup spike",
"kind": "threshold",
"metric": "events",
"event": "account_created",
"window_minutes": 60,
"op": "gte",
"threshold": 500,
"value": 812,
"at": "2026-08-11T10:00:00.000Z",
"text": "🚨 Signup spike — MyApp: account_created = 812 (≥ 500) in 1h"
}
Why the shaping exists
Because the chat providers each demand something different, and two of them are strict about it:
- Google Chat rejects any unknown field. Send it the full structured payload and the request fails outright.
- Slack requires
text. - Discord requires
content.
Sending one universal body would mean it fails on at least two of the three. So the generic payload carries both: the structured fields and a human-readable text string, which is the same sentence the chat providers get. If you’re writing your own receiver and you just want to forward the message somewhere, read text and ignore the rest.
Generic payload fields
| Field | Type | Meaning |
|---|---|---|
type | string | alert.fired, alert.recovered, or alert.test |
project | string | The project name |
rule | string | The rule name you gave it |
kind | string | threshold or absence |
metric | string | events or visitors |
event | string | null | The event being watched. null when the rule watches all events |
window_minutes | number | The evaluation window in minutes (60 = 1 hour, 1440 = 24 hours) |
op | string | null | gte or lte. null for absence rules |
threshold | number | null | The configured limit. null for absence rules |
value | number | The value found by this evaluation |
at | string | ISO 8601 UTC timestamp of the evaluation |
text | string | The full human-readable message |
Branch on type if you want to treat recoveries differently from firings, and always tolerate null in event, op, and threshold — an absence rule on all events sends null for all three.
Note that the notification text is always in English, regardless of the language you use the app in. The recipient of a webhook is unknown, so there’s nothing to localize against.
Setting up Google Chat
- Open the Google Chat space you want alerts in.
- From the space name menu, go to Apps & integrations → Webhooks → Add webhook.
- Give it a name (LaunchPulse) and save. Copy the URL — it’s on
chat.googleapis.com. - In LaunchPulse, open your project’s Alerts page, edit or create a rule, and paste the URL into the Webhook URL field.
- Save, then hit Send test. The message should appear in the space within a second.
Setting up Slack
- In Slack, create an incoming webhook for the channel you want — via a Slack app with Incoming Webhooks enabled, or the Incoming WebHooks integration.
- Copy the webhook URL. It’s on
hooks.slack.com. - Paste it into the rule’s Webhook URL field and save.
- Hit Send test.
Setting up Discord
- In Discord, open Server Settings → Integrations → Webhooks → New Webhook (or the channel’s Edit Channel → Integrations).
- Pick the channel, name it, and Copy Webhook URL. It’s on
discord.com. - Paste it into the rule’s Webhook URL field and save.
- Hit Send test.
For all three: if the test doesn’t arrive, check the recent activity feed on the alerts page. A delivery failed flag means we sent it and the receiver said no.
Why your webhook URL might be rejected
Webhook delivery is a server making an outbound request on your behalf, so the URL is validated hard. These rules exist to stop LaunchPulse being used to probe private networks.
https only. Plain http:// URLs are rejected at save time.
The host must be publicly reachable. Blocked hostnames include localhost, anything under .local or .internal, and cloud metadata hostnames. Blocked address ranges include:
| Range | What it is |
|---|---|
127.0.0.0/8 | Loopback |
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | Private networks |
169.254.0.0/16 | Link-local and cloud metadata |
100.64.0.0/10 | CGNAT |
| Multicast and reserved ranges | Not routable |
These are blocked in any notation — decimal, hex, octal, IPv6-mapped. Encoding 127.0.0.1 differently doesn’t get you through.
Validation happens again at delivery time. The URL is re-checked and DNS-resolved when the alert actually fires, and the connection is pinned to the address that was validated. That defeats DNS rebinding, where a hostname resolves to a public IP during the check and a private one a moment later.
10-second timeout. If your endpoint hasn’t responded in ten seconds, the delivery fails. Acknowledge fast and do your work asynchronously.
Redirects are not followed. Give us the final URL.
Any non-2xx response is a failed delivery. It’s recorded in the activity feed with the delivery failed flag. There is no retry — remember that alerts are edge-triggered, so a failed delivery on a state change is a missed message, not a delayed one.
Testing locally
You can’t. A tunnel or a LAN address will be rejected by the rules above, and so will http://localhost:3000.
Use a public https endpoint instead. A service like webhook.site gives you a URL in one click that shows you the exact request body we sent — headers, JSON, everything. It’s the fastest way to see a real payload before you write a line of receiver code. Once your receiver is deployed behind public https, point the rule at it and Send test again.