Skip to content

Summary

Introduction

REVER webhooks provide real-time notifications for key events in the return process. They allow businesses to automate responses throughout the return lifecycle.
By subscribing, you receive instant updates when return statuses change — including creation, shipping updates, completion, and cancellations.

Registration

Enable webhooks from your REVER dashboard, under Configuration → Webhooks, where you generate the shared secret used for signature verification. The secret belongs to your account and you can view it there at any time. The page is available to administrators of your account; if you do not see it, ask your account manager. To register or change the destination URL, contact your account manager as well.

Replacing the secret takes effect on the next webhook we send, so update your endpoint before you replace it. Deleting it turns signing off: deliveries continue, without the signature header.

Security and Authentication

Webhooks are sent to public endpoints — your server is responsible for validating that each request genuinely comes from REVER.

Every webhook request includes an X-REVER-Signature header, so you can confirm the request came from REVER and has not been replayed:

X-REVER-Signature: t=1735689600,v1=2ff43fd45fd71088d4a6e835b06814bf6bca295753e17cbcf8ed0e0f64932c00

The header has two components:

ComponentMeaning
tUnix timestamp, in seconds, of the moment we signed the request
v1Hex-encoded HMAC-SHA256 signature

The signed payload

The signature covers the timestamp and the body, joined by a single .:

signed_payload = t + "." + raw_request_body
v1             = hex( HMAC-SHA256( signed_payload, shared_secret ) )

Signing the timestamp along with the body is what lets you reject a replayed request by age: an attacker replaying a captured request cannot move t forward without invalidating the signature.

Verifying a request

  1. Read the t and v1 components from the header. Reject the request if either is missing or malformed — a well-formed header always carries both, v1 being 64 hex characters.
  2. Reject the request if t is older than your tolerance — five minutes is a reasonable default.
  3. Take the raw request body, exactly as received. Verify against it before any JSON parsing: re-serializing a parsed object changes key order or whitespace, and the signature will no longer match even though the payload is genuine.
  4. Compute HMAC-SHA256(t + "." + raw_body, shared_secret) and hex-encode it.
  5. Compare with v1 using a constant-time comparison.
import hashlib
import hmac
import time

def verify(header: str, raw_body: bytes, secret: bytes, max_age_seconds: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    if not parts.get("t", "").isdigit() or "v1" not in parts:
        return False  # malformed header: it did not come from us
    if abs(time.time() - int(parts["t"])) > max_age_seconds:
        return False  # too old: treat as a replay
    signed_payload = f'{parts["t"]}.'.encode() + raw_body
    expected = hmac.new(secret, signed_payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected.encode(), parts["v1"].encode())
const crypto = require('crypto')

function verify(header, rawBody, secret, maxAgeSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2)))
  if (!/^\d+$/.test(parts.t ?? '') || !/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) {
    return false // malformed header: it did not come from us
  }
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > maxAgeSeconds) {
    return false // too old: treat as a replay
  }
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.`)
    .update(rawBody)
    .digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

You can check your implementation against this vector, which produces the header shown above:

Secretwhsec_EXAMPLEexampleEXAMPLEexampleEXAMPLEexample
Body{"event_name":"onReturnProcessCompleted","process_id":"retp_2NkQ"}
t1735689600
v12ff43fd45fd71088d4a6e835b06814bf6bca295753e17cbcf8ed0e0f64932c00

Retries

A webhook we retry is signed again, with the timestamp of the retry — not the original. A retry is therefore a valid, fresh request rather than something your replay window should reject.

While you are adopting signatures

Until you generate a secret, your webhooks arrive without the X-REVER-Signature header, exactly as before. Add your verification, generate the secret, and only then start rejecting unsigned requests — otherwise you would reject your own traffic during the switch.

Download OpenAPI description
Languages
Servers
https://api.byrever.com
Webhooks