Documentation

Integrating revws.

Drop-in reviews for SaaS products. Your users sign in to your product; revws federates that identity over a signed JWT and renders threaded reviews against the page they're on. You never store the data; you never write moderation UI.

Quickstart

Three things to copy

Every product gives you a public id (pub_…), a signing secret (sk_…), and a page id you choose per host page. Wire them in three steps.

  1. 1

    Get your keys

    Create a product in the dashboard. You get a public id (pub_…) — safe to ship in client code — and a signing secret (sk_…) shown once. The secret stays on your server; rotate it any time.

  2. 2

    Mint a short-lived JWT per user

    Sign an HS256 JWT with your signing secret in the same handler that renders the page. Your users, your identity — revws never owns accounts.

    # Your backend mints a short-lived JWT per signed-in user.
    import jwt, time
    
    token = jwt.encode(
        {
            "iss": "pub_...",            # your product id
            "sub": user.id,               # YOUR user id
            "name": user.display_name,
            "exp": int(time.time()) + 600,
        },
        PRODUCT_SIGNING_SECRET,           # sk_... from the dashboard
        algorithm="HS256",
    )
  3. 3

    Drop the tag

    Add the script tag and a mount point. The widget mounts into <div id="revws">, fetches the thread, and renders.

    <script src="https://cdn.revws.io/v1/widget.js"
            data-product="pub_..."
            data-page="<your page id>"
            data-user-token="<signed JWT>"></script>
    <div id="revws"></div>

Auth federation

One signed token per user

The widget needs a token that says "the user looking at this page is X". You mint it server-side and hand it to the widget. Every claim:

ClaimRequiredDescription
issyesYour product's public id (pub_…)
subyes A stable id for the user in your system — never an email or PII
nameoptionalDisplay name shown next to the user's reviews
expyesExpiry — must be ≤ 15 minutes out

The signing secret stays server-side, always. It never ships to a browser and never appears in the token — the widget only ever sees the signed JWT. Ten minutes is the sweet spot for token lifetime; the hard cap is fifteen. A bad secret or an over-15-minute exp is a 401 from every call.

REST API

Server-to-server

For SSR, native mobile clients, agents posting on a user's behalf, or any context where the widget isn't loaded. Base URL https://api.revws.io/v1. Auth is the same federated JWT the widget uses, passed as Authorization: Bearer <token>.

MethodPathPurpose
GET/v1/threads List threads for a (product, page). Usually one per page.
POST/v1/reviews Write a review. Body: { thread_id, parent_id?, body, rating? }.
POST/v1/reactions Toggle a reaction. Body: { review_id, emoji }.

Ratings are a strict integer 1..5 on top-level reviews and forbidden on replies. Errors are { "detail": "<reason>" } with a 4xx status; a free workspace at its monthly cap gets 402 { "detail": { "code": "plan_limit_reached" } } until the next UTC month.

Widget embed

The embed contract

The widget reads its whole configuration from three data attributes on the script tag:

data-product
Your public id (pub_…).
data-page
Any string stable per host page. revws creates a thread per (product, page_id).
data-user-token
The signed JWT from your backend.

Default chrome is grayscale plus one accent — designed to disappear into your product. Theme it by overriding a single CSS variable, --revws-accent, on the mount point or any ancestor:

VariableEffect
--revws-accent Sets the widget's single accent color (stars, active controls, focus).

The bundle is < 30 KB gzipped, reads from edge cache, and targets a < 150 ms p95 first paint.

Key rotation

Rotating the signing secret

Rotate from Settings → Danger zone → Rotate signing secret on the product page (workspace admins only), or via the REST call:

POST /api/admin/products/{pid}/secret/rotate
Authorization: Bearer <workspace admin token>

Cutover is immediate — there is no dual-secret grace window. JWTs signed with the old secret stop verifying the moment you rotate. Update your server's REVWS_SIGNING_SECRET first, deploy, then rotate — or accept a brief window where signed-in users get a 401 until their page reloads with a fresh token. The new secret is shown once; every rotation and reveal writes an audit row.

Webhooks

Roadmap — not live yet

Event fanout

Nothing fires today. The contract below is a preview so you can plan for it; moderation runs in the dashboard for now.

When it ships: configure on your product's settings page. Every event — review.created, review.deleted, reaction.added, reaction.removed — is a signed POST to your endpoint, carrying an X-Revws-Signature: sha256=<hex> HMAC header (verify it before trusting the payload). The webhook secret is separate from the signing secret and rotates independently.

Ready to wire it up?

Private beta — keys granted within a day. No call, no demo gauntlet.