Skip to content

Authentication and signing

Each request carries three headers:

Header Value
Authorization Bearer rvw_live_…: the key. It says who’s calling.
X-Timestamp The current time in Unix seconds (1760630400).
X-Signature sha256= + the HMAC-SHA256 in hex, with the signing secret, of the string {X-Timestamp}.{raw body}.
string = X-Timestamp + "." + body
signature = "sha256=" + hex( HMAC_SHA256( secret, string ) )
  • The body is the exact text you send, byte for byte. If you sign a JSON and then serialize it again with different spacing, the signature won’t match.
  • On a GET or a POST without a body, the body is the empty string: you sign 1760630400..
  • The timestamp is part of the signature and we accept up to 5 minutes of difference from our clock. That way nobody can resend a captured request.
Ventana de terminal
KEY="rvw_live_..."
SECRET="rvwsig_..."
TS=$(date +%s)
BODY='{"order":{"shopify_order_id":"456"},"locale":"en"}'
SIGNATURE=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl https://reviews.leadit.tech/api/v1/review-requests \
-H "Authorization: Bearer $KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature: sha256=$SIGNATURE" \
-H "Content-Type: application/json" \
-d "$BODY"

Each key has a scope you choose when you create it, and it doesn’t change afterwards. To change it, create another one and revoke the old one. You can have up to 5 keys.

Scope In the app What it can do
read Read only All GETs.
moderate Requests and moderation The above, plus getting, confirming, sending now and cancelling requests; creating, moderating and replying to reviews; uploading photos and videos.
full Full access Everything: also deleting reviews, changing settings, retrying webhooks and bulk import.

A call outside the scope returns 403 insufficient_scope, with the scope it would need in details.

POST /reviews and POST /reviews/import accept the Idempotency-Key header (a UUID, for example). If you repeat the same request with the same key within 24 hours, we return the same response with the Idempotent-Replayed: true header, without creating anything again.

  • The same Idempotency-Key with a different body returns 422 idempotency_key_reused.
  • While the first one is still in progress, the second returns 409 idempotency_in_progress.
  • A 5xx error isn’t stored: you can retry.

POST /review-requests doesn’t need the header: if the order already has a request, it returns the same one with 200.

120 requests per minute per key. If you go over, we return 429 rate_limited with Retry-After: 60.