Developer Documentation

Webhooks

Receive real-time HTTP callbacks when RivalCheck detects competitor changes. Push competitive intelligence directly into Slack, Discord, Zapier, or your own systems.

Overview

RivalCheck monitors your competitors' websites for pricing changes, messaging shifts, feature updates, and layout redesigns. When a change is detected, our AI analyses its strategic significance and assigns a severity level. Webhooks let you push that intelligence to your team in real time, instead of waiting for email digests or manually checking the dashboard.

Every webhook delivery is an HTTP POST request to a URL you specify, containing a JSON payload with the change details, AI analysis, and competitor context. RivalCheck automatically formats payloads for Slack and Discord when it detects those endpoint URLs.

Common use cases

Slack / Discord alerts

Post competitor changes to a dedicated #competitive-intel channel the moment they're detected.

Zapier / Make automation

Trigger Zaps or scenarios that log changes to Google Sheets, Notion databases, or CRM records.

Custom dashboards

Feed change data into internal BI tools or competitive intelligence dashboards.

CRM updates

Automatically attach competitor pricing changes to open deals in Salesforce or HubSpot.

Webhooks vs polling the API

Webhooks API Polling
Latency Seconds after detection Depends on poll interval
Rate limits No limit (push-based) 1,000 requests/day
Setup effort Paste a URL Build a polling loop
Best for Real-time alerts, automation Batch processing, backfills

Setting Up Webhooks

1. Open your organisation settings

Navigate to Organisation Settings and scroll to the Webhooks section. Only organisation admins can configure webhooks.

2. Enter your endpoint URL

Paste any HTTPS URL. RivalCheck auto-detects Slack and Discord webhook URLs and formats payloads accordingly:

https://hooks.slack.com/services/... Slack Block Kit format
https://discord.com/api/webhooks/... Discord embed format
https://your-server.com/webhooks Generic JSON payload

Note: Only HTTPS endpoints are supported. Plain HTTP URLs will be rejected to protect your data in transit.

3. Generate a webhook secret

Click Generate Secret to create a signing key. RivalCheck uses this secret to compute an HMAC-SHA256 signature for every delivery, included in the X-RivalCheck-Signature header. Store this secret securely — you will need it to verify that incoming requests genuinely originate from RivalCheck.

4. Choose your filters (optional)

Select which event types and severity levels should trigger deliveries. By default, all events and all severity levels are enabled. See Severity Filtering and Event Filtering below.

5. Enable and test

Toggle the webhook to Enabled, then click Send Test to verify connectivity. You should receive a webhook.test event at your endpoint within seconds.

Event Types

EVENT change.detected

Fired when RivalCheck detects a meaningful change on a monitored competitor page. This is the most common event and the primary reason most teams configure webhooks. The payload includes the competitor name, page URL, change type (pricing, content, layout), severity level, and the AI-generated summary explaining the strategic significance.

When it fires

  • A scheduled snapshot reveals a difference from the previous snapshot
  • The AI analysis confirms the change is meaningful (cosmetic-only changes are filtered out)
  • The change is assigned a severity: major, moderate, or minor

Example payload (generic HTTPS)

{
  "event": "change.detected",
  "timestamp": "2026-03-24T09:15:22Z",
  "organisation": {
    "name": "Acme Corp"
  },
  "competitor": {
    "name": "CompetitorX",
    "website_url": "https://competitorx.com"
  },
  "change": {
    "id": "chg_a1b2c3d4e5",
    "type": "pricing",
    "severity": "major",
    "summary": "CompetitorX increased their Pro plan from $49/mo to $79/mo — a 61% price increase. The Enterprise tier was also restructured, removing the unlimited users benefit and capping at 50 seats. This is the first pricing change in 8 months.",
    "page_url": "https://competitorx.com/pricing",
    "page_type": "pricing",
    "detected_at": "2026-03-24T09:14:58Z",
    "url": "https://app.rivalcheck.com/app/changes/chg_a1b2c3d4e5"
  }
}

Fields reference

Field Type Description
event string Always "change.detected"
timestamp string ISO 8601 timestamp of when the webhook was sent
organisation.name string Your organisation name
competitor.name string Display name of the competitor
competitor.website_url string Root URL of the competitor website
change.id string Unique public identifier for the change (prefixed chg_)
change.type string pricing, content, or layout
change.severity string major, moderate, or minor
change.summary string AI-generated strategic analysis of the change (up to 300 characters)
change.page_url string URL of the monitored page where the change was detected
change.page_type string Page category: pricing, about, product, homepage, etc.
change.detected_at string ISO 8601 timestamp of when the change was first detected
change.url string Direct link to view the change in RivalCheck
EVENT battle_card.updated

Fired when a competitor's battle card is regenerated, either automatically after significant changes accumulate or when manually triggered by a team member. Use this to keep your sales team's battle card repository in sync.

When it fires

  • A battle card is regenerated after new changes are incorporated
  • A team member manually triggers regeneration from the competitor page or via the API

Example payload

{
  "event": "battle_card.updated",
  "timestamp": "2026-03-24T10:30:00Z",
  "organisation": {
    "name": "Acme Corp"
  },
  "competitor": {
    "name": "CompetitorX",
    "website_url": "https://competitorx.com"
  },
  "battle_card": {
    "competitor_id": "cmp_x1y2z3",
    "updated_at": "2026-03-24T10:29:55Z",
    "sections_updated": ["pricing_comparison", "strengths", "objection_handling"],
    "url": "https://app.rivalcheck.com/app/competitors/cmp_x1y2z3/battle_card"
  }
}
EVENT competitor.status_changed

Fired when a competitor's monitoring status transitions. Useful for detecting problems early (e.g. a competitor's site goes down) or knowing when newly added competitors are ready to monitor.

Status transitions

discovering → active AI discovery completed, pages identified and monitoring started
active → error Repeated snapshot failures (site down, blocking, DNS issues)
error → active Monitoring resumed after the issue resolved
active → paused Monitoring paused by a team member or due to plan limits

Example payload

{
  "event": "competitor.status_changed",
  "timestamp": "2026-03-24T06:00:12Z",
  "organisation": {
    "name": "Acme Corp"
  },
  "competitor": {
    "name": "CompetitorX",
    "website_url": "https://competitorx.com",
    "previous_status": "discovering",
    "new_status": "active",
    "pages_discovered": 5
  }
}
EVENT webhook.test

A synthetic event sent when you click Send Test in your organisation settings. Use it to verify your endpoint is reachable and correctly parsing payloads before any real changes are detected.

Example payload (generic HTTPS)

{
  "event": "webhook.test",
  "timestamp": "2026-03-24T14:00:00Z",
  "organisation": {
    "name": "Acme Corp"
  },
  "message": "Webhook configured successfully for Acme Corp. You'll receive notifications here when competitor changes are detected."
}

For Slack endpoints, this sends a simple { "text": "..." } message. For Discord, a { "content": "..." } message.

Payload Format

Generic (HTTPS)

For any HTTPS URL that is not a Slack or Discord webhook, RivalCheck sends a standard JSON payload. The request is a POST with Content-Type: application/json.

Headers

Content-Type application/json
User-Agent RivalCheck-Webhook/1.0
X-RivalCheck-Signature HMAC-SHA256 hex digest (present when a webhook secret is configured)

Full example with realistic competitive intelligence data

{
  "event": "change.detected",
  "timestamp": "2026-03-24T14:22:07Z",
  "organisation": {
    "name": "Acme Corp"
  },
  "competitor": {
    "name": "PlanForge",
    "website_url": "https://planforge.io"
  },
  "change": {
    "id": "chg_r8k2m5n7p1",
    "type": "content",
    "severity": "moderate",
    "summary": "PlanForge repositioned their homepage headline from 'Simple Project Management' to 'AI-Powered Project Intelligence'. The subheading now emphasises predictive analytics and automated resource allocation, suggesting a strategic pivot toward the AI/ML market segment that directly overlaps with Acme's Q2 positioning.",
    "page_url": "https://planforge.io",
    "page_type": "homepage",
    "detected_at": "2026-03-24T14:20:33Z",
    "url": "https://app.rivalcheck.com/app/changes/chg_r8k2m5n7p1"
  }
}

Slack (Block Kit)

When RivalCheck detects a hooks.slack.com URL, it formats payloads using Slack's Block Kit for rich formatting. Messages include a fallback text field for notifications and a blocks array for the full visual layout.

How it renders

The message displays as a structured card with a severity indicator, the competitor name in bold, two side-by-side fields showing the page and severity, the AI summary as body text, and a "View in RivalCheck" button that links directly to the change detail page. The severity level is prefixed with a coloured dot — red for major, yellow for moderate, blue for minor.

Example payload

{
  "text": "\ud83d\udd34 Major pricing change detected on *CompetitorX*",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "\ud83d\udd34 *Major pricing change* detected on *CompetitorX*"
      }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*Page:*\n<https://competitorx.com/pricing|Pricing>" },
        { "type": "mrkdwn", "text": "*Severity:*\nMajor" }
      ]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "CompetitorX increased their Pro plan from $49/mo to $79/mo \u2014 a 61% price increase..."
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "View in RivalCheck" },
          "url": "https://app.rivalcheck.com/app/changes/chg_a1b2c3d4e5"
        }
      ]
    }
  ]
}

Tip: Slack truncates message previews in notifications. The text field at the root ensures push notifications are meaningful even before the user opens the channel.

Discord (Embed)

For discord.com/api/webhooks URLs, RivalCheck uses Discord's embed format with colour-coded severity borders.

Colour coding

Major 0xEF4444 Red
Moderate 0xF59E0B Amber
Minor 0x3B82F6 Blue

Example payload

{
  "content": "\ud83d\udd34 **Major pricing change** detected on **CompetitorX**",
  "embeds": [
    {
      "title": "CompetitorX \u2014 Pricing Change",
      "description": "CompetitorX increased their Pro plan from $49/mo to $79/mo...",
      "url": "https://app.rivalcheck.com/app/changes/chg_a1b2c3d4e5",
      "color": 15684676,
      "fields": [
        { "name": "Page", "value": "[Pricing](https://competitorx.com/pricing)", "inline": true },
        { "name": "Severity", "value": "Major", "inline": true }
      ],
      "timestamp": "2026-03-24T09:14:58Z"
    }
  ]
}

The embed renders as a card with a coloured left border, a clickable title linking to RivalCheck, inline fields for page and severity, and a timestamp in the footer.

Severity Filtering

Not every change warrants an interruption. Severity filtering lets you control which changes trigger webhook deliveries based on their AI-assigned importance level.

Severity levels

major Significant strategic changes: pricing restructures, new product launches, major positioning shifts, feature removals
moderate Noteworthy updates: messaging tweaks, new testimonials or social proof, minor pricing adjustments, feature additions
minor Small changes: copy edits, design refinements, image swaps, footer updates

Configuration

In your organisation settings, select which severity levels should trigger deliveries. For example, selecting only major means you will only receive webhooks for significant strategic changes — perfect for a Slack channel where you want to minimise noise.

When no severity filter is configured (the default), all severity levels trigger deliveries.

Example: pricing-only major alerts

Set severity filter to major and event filter to change.detected. You will only be notified when competitors make significant pricing, positioning, or product changes — typically 1-3 alerts per week rather than dozens.

Event Filtering

Subscribe to specific event types to receive only the webhooks you care about. When no event filter is configured, all event types are delivered.

Available events

change.detected Competitor change detected and analysed
battle_card.updated Battle card regenerated with new intelligence
competitor.status_changed Monitoring status transition
webhook.test Test delivery (always delivered regardless of filter)

Configure event filters in your organisation settings by selecting the events you want to receive. The webhook.test event is always delivered regardless of your filter settings, so you can always verify connectivity.

Security — Signature Verification

How it works

Every webhook request from RivalCheck includes an X-RivalCheck-Signature header. This is an HMAC-SHA256 hex digest computed over the raw request body using your webhook secret as the key. To verify a webhook is authentic, compute the same digest on your end and compare the two values.

Verification steps

  1. Read the raw request body (do not parse it first — you need the exact bytes that were signed)
  2. Compute HMAC-SHA256(webhook_secret, raw_body) and hex-encode the result
  3. Compare your computed signature with the X-RivalCheck-Signature header using a timing-safe comparison function
  4. If they match, the request is authentic. If not, reject it with a 401 or 403 status.

Important: Always use a timing-safe comparison function (e.g. secure_compare, hmac.compare_digest, crypto.timingSafeEqual). Standard string equality (==) is vulnerable to timing attacks that can be used to reconstruct the secret byte by byte.

Ruby Signature verification
require "openssl"

def verify_webhook(request, secret)
  body      = request.body.read
  signature = request.headers["X-RivalCheck-Signature"]

  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
  verified = ActiveSupport::SecurityUtils.secure_compare(expected, signature)

  unless verified
    render json: { error: "Invalid signature" }, status: :forbidden
    return
  end

  payload = JSON.parse(body)
  # Process the webhook...
end
Python Signature verification
import hmac
import hashlib

def verify_webhook(request, secret):
    body = request.get_data(as_text=False)
    signature = request.headers.get("X-RivalCheck-Signature", "")

    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return {"error": "Invalid signature"}, 403

    payload = request.get_json()
    # Process the webhook...
    return {"ok": True}, 200
JavaScript Signature verification (Node.js)
const crypto = require("crypto");

function verifyWebhook(req, secret) {
  const body = req.rawBody; // requires raw body middleware
  const signature = req.headers["x-rivalcheck-signature"];

  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");

  const verified = crypto.timingSafeEqual(
    Buffer.from(expected, "utf8"),
    Buffer.from(signature, "utf8")
  );

  if (!verified) {
    return res.status(403).json({ error: "Invalid signature" });
  }

  const payload = JSON.parse(body);
  // Process the webhook...
}
Go Signature verification
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
)

func verifyWebhook(r *http.Request, secret string) ([]byte, bool) {
    body, _ := io.ReadAll(r.Body)
    signature := r.Header.Get("X-RivalCheck-Signature")

    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))

    return body, hmac.Equal([]byte(expected), []byte(signature))
}

Delivery & Retry Logic

RivalCheck makes a best effort to deliver every webhook. Here is exactly how the delivery pipeline works.

Connection timeout 10 seconds
Read timeout 10 seconds
Maximum retries 2 (3 attempts total)
Retry backoff Exponential: 1s after first failure, 2s after second
Success codes Any 2xx status (200, 201, 204, etc.)
Failure handling Logged server-side. Non-2xx responses and network errors trigger retries.

What triggers a retry

  • Connection timeout (Net::OpenTimeout)
  • Read timeout (Net::ReadTimeout)
  • Connection refused or reset
  • DNS resolution failure
  • Non-2xx HTTP response (e.g. 500, 502, 503)

What does not trigger a retry

  • Client errors (4xx) other than 408 and 429 — these indicate a configuration issue on your end
  • SSL certificate errors — fix your certificate before re-enabling

Tip: Your endpoint should return a 2xx response as quickly as possible. Do your heavy processing asynchronously after responding. If your endpoint consistently takes more than 10 seconds, deliveries will fail.

Integration Recipes

Slack

The most popular integration. Get competitor change alerts directly in your Slack workspace.

1. Create a Slack incoming webhook

Go to api.slack.com/messaging/webhooks and create a new webhook. Select the channel where you want competitor alerts to appear (we recommend creating a dedicated #competitive-intel channel).

2. Copy the webhook URL

It will look like https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

3. Paste it into RivalCheck

In your organisation settings, paste the URL into the webhook URL field. RivalCheck automatically detects it as a Slack endpoint and formats payloads using Block Kit.

4. Send a test

Click Send Test and check your Slack channel. You should see a confirmation message within seconds.

Tips for Slack

  • Use a dedicated channel like #competitive-intel to keep alerts separate from general chatter
  • Set severity filter to major only if you want to avoid alert fatigue
  • Pin important competitor alerts for the team to reference during sales calls
  • Invite your sales, product, and marketing leads to the channel

Discord

Ideal for teams that use Discord for internal communication.

1. Create a Discord webhook

In your Discord server, go to Server Settings → Integrations → Webhooks → New Webhook. Name it "RivalCheck" and select the target channel.

2. Copy the webhook URL

Click Copy Webhook URL. It will look like https://discord.com/api/webhooks/123456789/abcdef...

3. Paste and test

Paste the URL into RivalCheck organisation settings and send a test. Change alerts will appear as rich embeds with colour-coded severity borders — red for major, amber for moderate, blue for minor.

Zapier

Connect RivalCheck to 6,000+ apps without writing code.

1. Create a new Zap with "Webhooks by Zapier" trigger

Choose Webhooks by Zapier as the trigger app, then select Catch Hook as the trigger event. Zapier will generate a unique URL.

2. Paste the Zapier URL into RivalCheck

Copy the Zapier webhook URL and paste it into your RivalCheck organisation settings. Click Send Test so Zapier can detect the payload structure.

3. Add your action

In Zapier, map the fields from the RivalCheck payload to your destination. The JSON fields are available as change.type, change.severity, competitor.name, etc.

Popular Zap ideas

Webhook → Google Sheets

Log every competitor change to a spreadsheet for trend analysis

Webhook → Email

Send major changes as formatted emails to your leadership team

Webhook → Notion

Create database entries in your competitive intelligence workspace

Webhook → Salesforce

Attach pricing changes to open opportunities in your CRM

n8n / Make (Integromat)

Both n8n and Make support generic webhook triggers that work with RivalCheck out of the box.

1. Add a Webhook trigger node

In n8n, add a Webhook node. In Make, add a Custom Webhook module. Both will generate a URL.

2. Paste and test

Paste the URL into RivalCheck and send a test event. The automation tool will detect the JSON structure and make all fields available for mapping in subsequent nodes.

3. Parse the payload

RivalCheck sends a flat JSON object. Key fields to map: event, competitor.name, change.severity, change.summary, change.url.

Custom (Node.js / Express)

A complete webhook receiver with signature verification, ready to deploy.

const express = require("express");
const crypto = require("crypto");

const app = express();
const WEBHOOK_SECRET = process.env.RIVALCHECK_WEBHOOK_SECRET;

// Important: use raw body for signature verification
app.use("/webhooks/rivalcheck", express.raw({ type: "application/json" }));

app.post("/webhooks/rivalcheck", (req, res) => {
  // 1. Verify signature
  const signature = req.headers["x-rivalcheck-signature"];
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");

  if (!crypto.timingSafeEqual(
    Buffer.from(expected, "utf8"),
    Buffer.from(signature || "", "utf8")
  )) {
    console.error("Webhook signature verification failed");
    return res.status(403).json({ error: "Invalid signature" });
  }

  // 2. Respond immediately (process async)
  res.status(200).json({ received: true });

  // 3. Process the event
  const payload = JSON.parse(req.body);

  switch (payload.event) {
    case "change.detected":
      console.log(
        `[${payload.change.severity}] ${payload.competitor.name}: ` +
        `${payload.change.summary}`
      );
      // Add to database, notify team, update CRM, etc.
      break;

    case "battle_card.updated":
      console.log(`Battle card updated: ${payload.competitor.name}`);
      break;

    case "competitor.status_changed":
      console.log(
        `${payload.competitor.name}: ` +
        `${payload.competitor.previous_status} -> ${payload.competitor.new_status}`
      );
      break;

    case "webhook.test":
      console.log("Test webhook received successfully");
      break;
  }
});

app.listen(3000, () => console.log("Webhook receiver running on :3000"));

Testing

Using the test button

The easiest way to test your webhook. Navigate to your organisation settings and click Send Test. This sends a webhook.test event to your configured endpoint. For Slack and Discord, you will see a human-readable confirmation message. For generic endpoints, you will receive a full JSON payload.

Simulating with curl

To test your receiver locally without RivalCheck, simulate a webhook delivery with curl. This is useful during development before you have a publicly accessible URL.

# Generate signature
BODY='{"event":"change.detected","timestamp":"2026-03-24T09:15:22Z","organisation":{"name":"Test Org"},"competitor":{"name":"CompetitorX","website_url":"https://competitorx.com"},"change":{"id":"chg_test123","type":"pricing","severity":"major","summary":"Test webhook delivery","page_url":"https://competitorx.com/pricing","page_type":"pricing","detected_at":"2026-03-24T09:14:58Z","url":"https://app.rivalcheck.com/app/changes/chg_test123"}}'

SECRET="your_webhook_secret_here"
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

# Send the request
curl -X POST http://localhost:3000/webhooks/rivalcheck \
  -H "Content-Type: application/json" \
  -H "User-Agent: RivalCheck-Webhook/1.0" \
  -H "X-RivalCheck-Signature: $SIGNATURE" \
  -d "$BODY"

Debugging tips

  • Use webhook.site or RequestBin to inspect the raw request headers and body
  • Use ngrok to expose your local development server with a public HTTPS URL
  • Check that your endpoint returns a 2xx status within 10 seconds
  • If signature verification fails, ensure you are reading the raw request body (not a parsed/re-serialised version)
  • Verify your webhook secret matches exactly — no trailing whitespace or newlines

Troubleshooting

Webhook not arriving

  • Confirm the webhook is set to Enabled in your organisation settings
  • Check that your event and severity filters are not excluding the event type you expect
  • Verify the URL is correct and uses HTTPS
  • Click Send Test to confirm basic connectivity

SSL certificate errors

  • RivalCheck validates SSL certificates on your endpoint. Self-signed certificates will cause delivery failures.
  • Ensure your certificate chain is complete (includes intermediate certificates)
  • Check that your certificate has not expired
  • Use SSL Labs to diagnose certificate issues

Timeout errors

  • RivalCheck waits 10 seconds for a response. If your endpoint takes longer, the delivery is marked as failed.
  • Return a 2xx response immediately, then process the payload asynchronously
  • Avoid making external API calls (database writes, Slack posts, etc.) before responding

Firewall / network issues

  • Ensure your firewall allows inbound HTTPS traffic from external sources
  • If you use IP allowlisting, contact support@rivalcheck.com for our current egress IP ranges
  • Verify your DNS is resolving correctly and your server is reachable from the public internet

Signature verification failing

  • Ensure you are computing the HMAC over the raw request body bytes, not a parsed-then-re-serialised version (JSON key ordering and whitespace matter)
  • Check that your stored webhook secret matches exactly what is configured in RivalCheck
  • Verify you are reading the X-RivalCheck-Signature header (case-insensitive match)
  • If using a reverse proxy (nginx, Cloudflare), ensure it is not modifying the request body

Best Practices

01

Use HTTPS only

Webhook payloads contain competitive intelligence data. Always use HTTPS to encrypt data in transit. RivalCheck rejects plain HTTP URLs.

02

Always verify signatures

Generate a webhook secret and verify the X-RivalCheck-Signature header on every request. This prevents attackers from sending fake competitive intelligence to your systems.

03

Respond quickly (under 5 seconds)

Return a 2xx response as soon as you have received and persisted the payload. Do heavy processing (Slack forwarding, CRM updates, AI analysis) in a background job or async worker.

04

Process asynchronously

Queue the webhook payload for background processing. This makes your endpoint fast and resilient to downstream failures.

05

Handle duplicates idempotently

In rare cases (retries, network issues), you may receive the same event more than once. Use the change.id field as a deduplication key. Before processing, check if you have already handled that change ID.

06

Monitor your webhook endpoint

Set up alerting on your endpoint's error rate and response time. A silently failing webhook means you are missing competitor intelligence. Consider logging all incoming webhook payloads for debugging.

07

Use severity filtering to reduce noise

If your team is experiencing alert fatigue, configure severity filters to only deliver major changes to Slack, while logging all changes to a less-visible destination like Google Sheets.

08

Rotate secrets periodically

Generate a new webhook secret every 90 days. Update your receiver first, then rotate the secret in RivalCheck to avoid downtime.