Home/Docs/Webhooks

Webhooks

React the moment a refresh lands.

Register an endpoint and Quorel will POST to it the moment a dataset refresh completes. No polling. Available on Pro and Scale.

How it works

One webhook per dataset. When a refresh completes and a new version is created, Quorel sends a single POST to your registered URL with a JSON payload describing the event. The result is recorded in last_fired_at and last_status.

01

Expose a public endpoint

Your server needs a publicly accessible URL that accepts POST requests with a JSON body. It must return a 2xx status code within a reasonable timeout.

02

Register the webhook

Call POST /webhook/register with your dataset_id, endpoint URL, and an optional secret. One webhook per dataset — registering again replaces the existing one.

03

Verify the signature (recommended)

If you provided a secret, Quorel signs every payload with it. Verify the signature on your server before processing the event.

04

Respond with 2xx

Return any 2xx status code quickly. Quorel records the status in last_status. Non-2xx responses are noted but the webhook is not automatically disabled.

Endpoints

All three endpoints require session authentication — they are called from your dashboard or your own backend, not from your webhook receiver.

POST/webhook/register

Register or update a webhook for a dataset. If a webhook already exists for this dataset, it is replaced. The secret is optional but recommended.

Auth: Session cookie

Request body

{
  "dataset_id": 42,
  "url": "https://your-server.com/webhook",
  "secret": "your_signing_secret"
}

Response

{ "ok": true }
DELETE/webhook/delete

Remove the webhook registered for a dataset. The endpoint will no longer receive POST requests after a refresh.

Auth: Session cookie

Request body

{ "dataset_id": 42 }

Response

{ "ok": true }
GET/webhook/view?dataset_id=42

Check whether a webhook exists for a dataset and inspect its status.

Auth: Session cookie

Response

{
  "has_webhook": true,
  "url": "https://your-server.com/webhook",
  "has_secret": true,
  "is_active": true,
  "last_fired_at": "2026-06-01T03:12:44Z",
  "last_status": 200,
  "created_at": "2026-05-15T10:00:00Z"
}

Registration fields

dataset_idnumberrequired

The dataset to attach the webhook to. Must be owned by the authenticated user.

urlstringrequired

The endpoint that will receive POST requests. Must be a valid http or https URL.

secretstring

An optional string used to sign the payload. See the signature verification section below.

Payload

Quorel sends a single POST to your endpoint with Content-Type: application/json. The body looks like this:

{
  "dataset_id": 42,
  "dataset_name": "remote-jobs",
  "version": 15,
  "entity_count": 847,
  "refreshed_at": "2026-06-01T03:12:44Z"
}
dataset_idnumber

The numeric ID of the dataset that refreshed.

dataset_namestring

The display name of the dataset.

versionnumber

The version number that was just created by the refresh.

entity_countnumber

The number of entities in the new version.

refreshed_atstring

ISO 8601 timestamp of when the refresh completed.

Signature verification

If you registered a secret, Quorel includes an X-Quorel-Signature header on every request. It is a hex-encoded HMAC-SHA256 of the raw request body, keyed with your secret. Verify it before processing the payload.

Node.js

import crypto from "crypto";

function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Python

import hmac, hashlib

def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Always use a constant-time comparison function to prevent timing attacks. Never compare strings with === or ==.

View response fields

has_webhookboolean

Whether a webhook is registered for this dataset.

urlstring

The registered endpoint URL.

has_secretboolean

Whether a signing secret is configured. The secret itself is never returned.

is_activeboolean

Whether the webhook is active. Webhooks are active by default on registration.

last_fired_atstring | null

ISO 8601 timestamp of the last time the webhook was fired. Null if never fired.

last_statusnumber | null

The HTTP status code returned by your endpoint on the last attempt. Null if never fired.

created_atstring

ISO 8601 timestamp of when the webhook was registered.

If no webhook is registered, the view endpoint returns { "has_webhook": false }.

Plan availability

Free

Webhooks are not available. Register via the API will return 403.

Pro

Webhooks are available. One webhook per dataset.

Scale

Webhooks are available. One webhook per dataset.

See the full pricing page for a complete feature comparison.

Notes

One webhook per dataset

Registering a second webhook for the same dataset replaces the first. There is no queue of multiple endpoints.

No automatic retries

Quorel fires the webhook once and records the result. If your endpoint is down, you will not receive the event retroactively. Use the API to fetch the new version manually if needed.

Non-2xx responses are recorded, not fatal

If your endpoint returns 4xx or 5xx, the webhook is not disabled. The status is recorded in last_status and the next refresh will fire again.

Secret is write-only

The secret you provide is never returned by the view endpoint. has_secret tells you whether one is configured.

Registering replaces, not stacks

Calling POST /webhook/register when a webhook already exists updates the URL and secret in place. is_active is reset to true and last_fired_at is cleared.

Next steps