Integrations & API
Webhooks
Receive a signed HTTPS POST when visibility changes significantly, an audit finishes, content is published or a workflow sends one.
A webhook is a URL on your side that Rixe calls when something happens. Instead of polling the REST API, your system gets a signed JSON message the moment an event occurs. Use webhooks to feed Zapier, Make, Slack bots, your own backend or a data pipeline.
Where to find it
Open the user menu at the bottom of the sidebar, go to Settings › Developer and scroll to the Webhooks card. Connections › Webhooks points to the same place.
Webhooks are available on every plan. Only owners and admins can add or delete them.
Add a webhook
- In the Webhooks card, enter your endpoint in URL. It must be a public
https://address; plainhttp, private network addresses and hosts that cannot be resolved are refused. - Tick the events you want to receive (all four are ticked by default).
- Click Add.
- Copy the signing secret from the yellow notice. It starts with
whsec_. The signing secret is shown only now; store it with your receiver, because you need it to verify every call.
The list then shows each endpoint with its events, its state (active or off) and the last error, if any. Delete removes an endpoint.
Adding a URL that is already in the list updates its events and switches it back on. If you need a new signing secret, delete the endpoint and add it again.
Events
| Event | When it is sent | data contains |
|---|---|---|
significant-change | A notification of the Significant change type is raised: a visibility or rank threshold was crossed, a rival surged, a new source appeared. | notificationId, workspaceId, workspaceName, title, body |
audit-completed | A technical site audit finishes. | workspaceId, url, score |
content-published | Content was published to your site through a CMS connection. | workspaceId, contentId, url |
workflow-finished | A workflow reaches a Send webhook step. | workflowId, workflowName, label, runId, partial, summary, findings and, unless switched off in the step, context |
significant-change also follows your notification settings: under Settings › Notifications, the Significant change row has a Webhook column, which is on by default. If you turn it off there, that event is not sent to any webhook. The title and body are written in your organization's language. See Alerts and inbox.
workflow-finished is only sent by workflows that contain the Send webhook step. Because it is sent from inside the run, partial is true: steps after it can still change the outcome. The step can be set to send only when there are findings. See Autopilot and workflows.
What a delivery looks like
Rixe sends a POST with a JSON body:
POST /rixe-hook HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
Rixe-Event: audit-completed
Rixe-Signature: t=1761379200,v1=5f0c8e6b2a…
{"event":"audit-completed","createdAt":"2026-10-25T08:00:00.000Z","data":{"workspaceId":"9b1f6c2e-4d1a-4f0e-8a51-2f3c7d9e1a10","url":"https://flowcrm.io","score":82}}| Part | Meaning |
|---|---|
Rixe-Event header | The event name, so you can route without parsing the body. |
Rixe-Signature header | t=<unix seconds>,v1=<hex HMAC-SHA256>. See below. |
event | The event name again. |
createdAt | When the delivery was made, ISO 8601 in UTC. |
data | The event's fields from the table above. |
A significant-change body looks like this:
{
"event": "significant-change",
"createdAt": "2026-10-25T07:15:00.000Z",
"data": {
"notificationId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"workspaceId": "9b1f6c2e-4d1a-4f0e-8a51-2f3c7d9e1a10",
"workspaceName": "FlowCRM – US",
"title": "…",
"body": "…"
}
}Verify the signature
The signature works like Stripe's. Rixe takes the timestamp t, joins it to the raw request body with a dot (t + "." + body), and computes an HMAC-SHA256 of that string with your full signing secret (including the whsec_ prefix) as the key. The hex digest is v1.
To verify a call:
- Read the raw body exactly as received. Do not parse and re-serialise the JSON first; a single changed space breaks the signature.
- Split the
Rixe-Signatureheader on commas and readtandv1. - Reject the call if
tis more than 5 minutes away from your clock. This stops a captured call from being replayed later. - Compute HMAC-SHA256 of
t + "." + rawBodywith your secret and compare it tov1in constant time.
Node.js (Express)
import crypto from 'node:crypto';
import express from 'express';
const SECRET = process.env.RIXE_WEBHOOK_SECRET!; // whsec_...
const app = express();
function verify(rawBody: string, header: string, secret: string, toleranceSeconds = 300): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1 ?? '', 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/rixe-hook', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
if (!verify(raw, req.header('rixe-signature') ?? '', SECRET)) return res.sendStatus(400);
const event = JSON.parse(raw);
// Handle event.event / event.data here, then answer quickly.
res.sendStatus(200);
});Python (Flask)
import hashlib, hmac, os, time
from flask import Flask, request, abort
SECRET = os.environ["RIXE_WEBHOOK_SECRET"].encode() # whsec_...
app = Flask(__name__)
def verify(raw: bytes, header: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
@app.post("/rixe-hook")
def rixe_hook():
if not verify(request.get_data(), request.headers.get("Rixe-Signature", "")):
abort(400)
event = request.get_json()
return "", 200Delivery rules
- Answer with a 2xx status within 15 seconds. Anything else, including a timeout, counts as a failure.
- Redirects are not followed. A
301or302is a failure; give Rixe the final URL. - Each event is attempted once per endpoint. A failed delivery is recorded (the last error shows in the list) but is not sent again, so do slow work after you have answered.
- Ten consecutive failures switch the endpoint off. Its state changes to off and nothing more is sent. A single success resets the count. After fixing your receiver, add the same URL again to switch it back on.
- Deliveries can arrive in any order. Use
createdAtand the ids indataif order matters, and make your handler safe to run twice for the same id.
Zapier and Make
No custom app is needed. In Zapier, use a Webhooks by Zapier › Catch Hook trigger; in Make, a Custom webhook module. Paste the URL they give you into the Webhooks card. Zapier and Make cannot check the signature on their own, so treat the URL as a secret. To read data back from Rixe in the same scenario, call the REST API with an API key.
Common questions
My endpoint shows "off". It failed ten times in a row. Check the last error under the URL, fix the receiver, then add the same URL again.
I lost the signing secret. It cannot be shown again. Delete the endpoint and add it again to get a new one, then update your receiver.
I get significant-change too often, or not at all. It follows Settings › Notifications. Check the Webhook column of the Significant change row.
Related pages
Still stuck? Write to [email protected] and a person will answer.