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

  1. In the Webhooks card, enter your endpoint in URL. It must be a public https:// address; plain http, private network addresses and hosts that cannot be resolved are refused.
  2. Tick the events you want to receive (all four are ticked by default).
  3. Click Add.
  4. 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

EventWhen it is sentdata contains
significant-changeA 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-completedA technical site audit finishes.workspaceId, url, score
content-publishedContent was published to your site through a CMS connection.workspaceId, contentId, url
workflow-finishedA 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:

http
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}}
PartMeaning
Rixe-Event headerThe event name, so you can route without parsing the body.
Rixe-Signature headert=<unix seconds>,v1=<hex HMAC-SHA256>. See below.
eventThe event name again.
createdAtWhen the delivery was made, ISO 8601 in UTC.
dataThe event's fields from the table above.

A significant-change body looks like this:

json
{
  "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:

  1. Read the raw body exactly as received. Do not parse and re-serialise the JSON first; a single changed space breaks the signature.
  2. Split the Rixe-Signature header on commas and read t and v1.
  3. Reject the call if t is more than 5 minutes away from your clock. This stops a captured call from being replayed later.
  4. Compute HMAC-SHA256 of t + "." + rawBody with your secret and compare it to v1 in constant time.

Node.js (Express)

ts
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)

python
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 "", 200

Delivery rules

  • Answer with a 2xx status within 15 seconds. Anything else, including a timeout, counts as a failure.
  • Redirects are not followed. A 301 or 302 is 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 createdAt and the ids in data if 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.

Still stuck? Write to [email protected] and a person will answer.