Skip to main content
3 min read

Slack incoming webhook

Get a Slack message every time a Zenovay event fires — traffic spike, goal completion, error spike, or downtime.

This recipe uses Slack's native incoming webhook feature: no Slack app or bot needed.

Outbound webhooks are a Pro+ feature. Upgrade your plan to enable them.


1. Create the Slack incoming webhook

  1. In Slack, open your workspace → Apps → search "Incoming Webhooks" → Add to Slack.
  2. Pick the channel that should receive Zenovay alerts (e.g. #alerts or #growth).
  3. Click Add Incoming Webhooks integration.
  4. Slack shows a Webhook URL that looks like https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX — copy it.

2. Add the webhook in Zenovay

  1. In app.zenovay.com, go to Settings → Webhooks.
  2. Pick the website you want to receive notifications for.
  3. Click Add webhook.
  4. Fill in:
    • Name: Slack #alerts (or whatever's meaningful to you)
    • URL: paste the Slack webhook URL from Step 1
    • Events: pick the ones you care about (Traffic spike, Goal completed, Website down, …)
  5. Click Create webhook.
  6. Click the Send test event (paper-plane icon) on the new card. Within ~5 seconds you should see a Slack message in the channel.

That's it for the basic setup — Slack will accept Zenovay's default JSON payload as a generic text field.


3. (Optional) Pretty-format the Slack message

Slack's incoming webhooks accept a richer payload with blocks, attachments, and Markdown formatting. Zenovay sends a generic JSON event — Slack will render it as a fallback text value.

If you want pretty Slack messages with severity-coloured attachments or block layouts, run a tiny relay between Zenovay and Slack:

// Cloudflare Workers / any Node-style runtime
export default {
  async fetch(req) {
    const event = await req.json();
    const slackPayload = {
      text: `📈 Zenovay event: *${event.event_type}* on ${event.website_id}`,
      attachments: [{
        color: event.event_type === 'website_down' ? '#dc2626' : '#3b82f6',
        fields: [
          { title: 'Event', value: event.event_type, short: true },
          { title: 'Time', value: event.timestamp, short: true },
        ],
      }],
    };
    await fetch(SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(slackPayload),
    });
    return new Response('ok');
  },
};

Point Zenovay's webhook URL at your relay instead of Slack directly.


If you use a relay, verify Zenovay's HMAC-SHA256 signature so Slack only receives genuine Zenovay events:

import { createHmac } from 'crypto';

const signature = req.headers.get('x-zenovay-signature'); // 'sha256=<hex>'
const provided = signature.replace(/^sha256=/, '');
const expected = createHmac('sha256', YOUR_WEBHOOK_SECRET)
  .update(rawBody)
  .digest('hex');

if (provided !== expected) {
  return new Response('invalid signature', { status: 401 });
}

Your webhook secret is shown in Settings → Webhooks → click the eye icon on the webhook card.


Troubleshooting

  • No Slack message arrives: check the webhook card in Zenovay for a "Last fired" timestamp — if it's empty, no event has triggered yet. Click Send test event to force one.
  • invalid_payload from Slack: Slack's webhook returned a 400 because the JSON shape didn't match. Use a relay (Step 3) to reshape into Slack's expected format.
  • Spammy alerts: in the webhook config, deselect chatty event types like traffic_spike. Keep only the high-signal ones.

Was this page helpful?