SDKs & tools
Official, dependency-free clients for the Repuso Monitoring API. Each is a single file you drop into your project - auth is handled for you. Get your API key from the console.
Quick start
// list recent 4-5 star reviews (each item carries a sentiment label) import { Repuso } from './repuso.js'; const repuso = new Repuso('YOUR_API_KEY'); const { items } = await repuso.reviews.list({ profiles: '123', min_rating: 4 }); // AI insights: sentiment breakdown + strengths + opportunities const insights = await repuso.profiles.insights(123); console.log(insights.sentiment.positive_pct, insights.strengths); // safe retries - the same key never creates a duplicate await repuso.reviews.create( { from_name: 'Jane', text: 'Great!', rating_value: 5, rating_scale: 5 }, 'order-9982' );
from repuso import Repuso
repuso = Repuso("YOUR_API_KEY")
reviews = repuso.reviews.list(profiles="123", min_rating=4, translate="es")
insights = repuso.profiles.insights(123)
print(insights["sentiment"]["positive_pct"], insights["strengths"])require 'Repuso.php';
$repuso = new Repuso\Client('YOUR_API_KEY');
$reviews = $repuso->reviews->list(['profiles' => '123', 'since' => '2026-01-01']);
$insights = $repuso->profiles->insights(123);Pagination
Lists return a next_before_ts and next_before_id cursor. Pass both back for the next page - together they guarantee no review is skipped, even when many share the same timestamp.
let params = { profiles: '123', limit: 100 };
let page = await repuso.reviews.list(params);
while (page.items.length) {
process(page.items);
if (!page.next_before_id) break;
page = await repuso.reviews.list({ ...params, before_ts: page.next_before_ts, before_id: page.next_before_id });
}params = {"profiles": "123", "limit": 100}
page = repuso.reviews.list(**params)
while page["items"]:
process(page["items"])
if not page.get("next_before_id"):
break
page = repuso.reviews.list(**params, before_ts=page["next_before_ts"], before_id=page["next_before_id"])$params = ['profiles' => '123', 'limit' => 100];
$page = $repuso->reviews->list($params);
while (!empty($page['items'])) {
process($page['items']);
if (empty($page['next_before_id'])) break;
$page = $repuso->reviews->list($params + ['before_ts' => $page['next_before_ts'], 'before_id' => $page['next_before_id']]);
}Verifying webhook signatures
Every delivery includes X-Repuso-Signature: t=<unix>,v1=<hex> where <hex> is HMAC-SHA256(secret, "<t>.<raw body>"). The signing secret is shown once when you create the webhook. Always use a constant-time compare and reject stale timestamps.
// Node
import crypto from 'crypto';
function verify(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map(kv => kv.split('=')));
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}import hashlib, hmac, time
def verify(raw_body, header, secret, tolerance_sec=300):
parts = dict(kv.split("=", 1) for kv in header.split(",") if "=" in kv)
if "t" not in parts or "v1" not in parts:
return False
if abs(time.time() - int(parts["t"])) > tolerance_sec:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.{raw_body}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])// built into the PHP client
require 'Repuso.php';
$ok = Repuso\_Webhooks::verifySignature(
file_get_contents('php://input'),
$_SERVER['HTTP_X_REPUSO_SIGNATURE'] ?? '',
$secret
);
Every endpoint is documented in the API reference. The clients also expose a generic
request(method, path, opts) escape hatch for anything not wrapped as a named helper.