← Guides

Send new reviews to Slack

A webhook, a signature check and a Slack message. No polling.

The most common first integration: a one-star review appears somewhere and the team hears about it in a channel rather than a week later. Repuso pushes the event; you forward it.

New here?

Repuso collects customer reviews from Google, TripAdvisor, Trustpilot, the App Store and 50+ other platforms, and serves them through one REST API in a single JSON schema - with rating history, sentiment, AI summaries and webhooks on new reviews. You point it at a public profile; it keeps that profile current and tells you when something changes.

This guide assumes you already have an API key and at least one profile connected. Both take about two minutes: start with the quickstart, or get a key in the console - the trial is 10 days and needs no card.

1. Register the endpoint

curl -u :$REPUSO_KEY \
  -X POST https://api.repuso.com/public/v1/webhooks/add \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://yourapp.com/hooks/repuso", "events": ["review.created"]}'

The response contains a signing secret. It is shown once - store it now, because you cannot read it back later.

Three events are available: review.created, rating.changed and profile.error. The last one tells you a profile stopped collecting, usually because a connection expired - worth routing somewhere too.

2. What arrives

{
  "id": "evt_90514",
  "event": "review.created",
  "occurred_on": "2026-09-16 14:22:00",
  "data": {
    "id": 11109385,
    "profile_id": 49331,
    "profile_name": "The Savoy",
    "platform": "tripadvisor",
    "rating_value": 2,
    "rating_scale": 5,
    "sentiment": "negative",
    "text": "Room was not ready until late afternoon.",
    "from_name": "Alex M.",
    "posted_on": "2026-09-16 14:20:00"
  }
}

3. Verify the signature before trusting it

Every delivery carries a header:

X-Repuso-Signature: t=1789552920,v1=6f1c...c3a9

v1 is an HMAC-SHA256 of t + "." + raw_body, keyed with your secret. Compute it over the raw body - parse the JSON afterwards, or a re-serialised copy will never match.

import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    ts, sig = parts.get("t", ""), parts.get("v1", "")

    if abs(time.time() - int(ts)) > tolerance:      # reject replays
        return False

    expected = hmac.new(secret.encode(),
                        f"{ts}.".encode() + raw_body,
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)       # constant time

Use a constant-time comparison, and reject anything whose timestamp is outside a few minutes. A plain == leaks timing information; without the timestamp check, a captured delivery can be replayed forever.

4. Forward it to Slack

import requests

SLACK = "https://hooks.slack.com/services/T000/B000/xxxx"
STARS = lambda n, scale: "\u2b50" * round(n * 5 / scale)

def handle(event):
    d = event["data"]
    if d["rating_value"] / d["rating_scale"] > 0.6:   # only the ones that need a human
        return

    requests.post(SLACK, json={"text":
        f"{STARS(d['rating_value'], d['rating_scale'])} *{d['profile_name']}* "
        f"({d['platform']})\n>{d['text']}\n_{d['from_name']}, {d['posted_on']}_"})

Filtering server-side keeps the channel readable: a busy account gets dozens of five-star reviews a day and nobody needs a ping for each. Alert on the ones that need a reply.

5. Test it without waiting for a review

curl -u :$REPUSO_KEY -X POST https://api.repuso.com/public/v1/webhooks/test/123
curl -u :$REPUSO_KEY https://api.repuso.com/public/v1/webhooks/deliveries/123

The first sends a sample delivery; the second shows recent attempts with their status codes, which is the fastest way to see why a handler is failing.

What happens when your endpoint is down

Deliveries are retried three times per cycle across three cycles. After that the event is dropped, and an endpoint that keeps failing is disabled automatically - so a handler returning 500 for a week stops being called rather than being retried forever.

Two habits that avoid this: return 200 as soon as you have the payload and do the work asynchronously, and treat id as an idempotency key, since a retry after a timeout can deliver the same event twice.

Get an API key Sync reviews to a database

← All guides