Developer Documentation
Receive real-time HTTP callbacks when RivalCheck detects competitor changes. Push competitive intelligence directly into Slack, Discord, Zapier, or your own systems.
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 |
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.
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
major, moderate, or minorExample 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 |
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
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"
}
}
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
}
}
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.
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"
}
}
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.
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.
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.
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.
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
HMAC-SHA256(webhook_secret, raw_body) and hex-encode the resultX-RivalCheck-Signature header using a timing-safe comparison function401 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.
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
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
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...
}
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))
}
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
Net::OpenTimeout)Net::ReadTimeout)What does not trigger a retry
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.
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
#competitive-intel to keep alerts separate from general chattermajor only if you want to avoid alert fatigueIdeal 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.
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
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.
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"));
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
Webhook not arriving
SSL certificate errors
Timeout errors
Firewall / network issues
Signature verification failing
X-RivalCheck-Signature header (case-insensitive match)Use HTTPS only
Webhook payloads contain competitive intelligence data. Always use HTTPS to encrypt data in transit. RivalCheck rejects plain HTTP URLs.
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.
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.
Process asynchronously
Queue the webhook payload for background processing. This makes your endpoint fast and resilient to downstream failures.
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.
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.
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.
Rotate secrets periodically
Generate a new webhook secret every 90 days. Update your receiver first, then rotate the secret in RivalCheck to avoid downtime.
Related resources
Need help? support@rivalcheck.com
We use essential cookies to keep the site working. With your consent, we may also use analytics cookies to improve the experience. Privacy policy.