Webhooks
Instead of polling the API every minute, you get a POST when something new appears. URLs per plan: 2 on Creator, 10 on Pro, 50 on Business.
Events
news.publishedA new news item with gravity at least the URL'smin_gravity, 50 by default.law.digestThe day's legal digest is ready.fuel.capANRE has published a new cap.rates.updatedBNM has published the rate of the day.price.changedThe price of a tracked product changed in a shop. A URL lists up to 500 products initems. The comparison runs every 30 minutes.
What you receive
A POST with JSON and two headers: Satelit-Event and Satelit-Signature.
{
"id": "evt_01J9ZK3Q",
"type": "price.changed",
"created_at": "2026-11-02T13:30:00+02:00",
"data": {
"item": 18342,
"store": "kaufland",
"old": 17.19,
"new": 16.05,
"at": "2026-11-02T13:12:00+02:00"
}
}Verify the signature
The header looks like this: t=<unix>,v1=<signature>. The signature is HMAC-SHA256 with the URL's secret, over t.body: the timestamp, a dot and the exact, unchanged body. Reject a timestamp older than 5 minutes to stop replays.
The secret appears only once, when you create the URL in the dashboard. Keep it on your server.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as it arrived, before any JSON parsing
export function verify(rawBody, header, secret, tolerance = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > tolerance) return false;
const expected = Buffer.from(createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"));
const given = Buffer.from(parts.v1 ?? "");
return expected.length === given.length && timingSafeEqual(expected, given);
}
import hashlib
import hmac
import time
# raw_body: the request body exactly as it arrived, as bytes
def verify(raw_body, header, secret, tolerance=300):
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", 0))
if abs(time.time() - t) > tolerance:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
<?php
// $rawBody: the request body exactly as it arrived: file_get_contents("php://input")
function verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
$parts = [];
foreach (explode(",", $header) as $part) {
[$k, $v] = array_pad(explode("=", $part, 2), 2, "");
$parts[$k] = $v;
}
$t = (int) ($parts["t"] ?? 0);
if (abs(time() - $t) > $tolerance) {
return false;
}
$expected = hash_hmac("sha256", $t . "." . $rawBody, $secret);
return hash_equals($expected, $parts["v1"] ?? "");
}
How delivery works
- HTTPS only, to public addresses. Redirects are not followed.
- We wait 5 seconds. A 2xx response means received.
- Three attempts in 15 minutes.
- After 20 failures in a row the URL is switched off. You turn it back on from the dashboard.
- Each event has an
id. Keep it so you can ignore duplicates.
Test
From the dashboard you send a test event and see the latest deliveries, with their status.