Webhooks and signature verification
Webhooks push events to your CRM or automation tool the moment they happen. They are part of the Agency 1000 plan. Add an endpoint under Settings › Integrations, choose the events, and copy the signing secret. It is shown once.
Events
| Event | Sent when |
|---|---|
preview.ready | A preview finished generating and is live. |
preview.failed | A preview could not be generated; the credit was refunded. |
prospect.first_view | The prospect opened the preview for the first time. |
prospect.hot | Hot Lead Radar scored the prospect as hot. |
pin.created | The prospect dropped a feedback pin. |
quote.requested | The prospect asked for a quote from the Approve modal. |
project.approved | The prospect approved the redesign. |
batch.completed | Every row of a bulk CSV batch has finished. |
export.ready | A requested export is ready to download. |
hosted_site.published | A hosted client site was published or updated. |
hosted_site.unpublished | A hosted client site was taken offline. |
form.submitted | A visitor submitted a contact form handled by Form Relay. |
Use Send test event in Settings to receive a ping with the same headers and signature.
Delivery
Every delivery is an HTTPS POST with a JSON body and these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | UIRevamper-Webhooks/1.0 |
X-Revamp-Event | The event type, or ping for the test event. |
X-Revamp-Event-Id | Stable across retries and redeliveries: deduplicate on it. |
X-Revamp-Delivery | Unique per delivery attempt. |
X-Revamp-Timestamp | Unix seconds when the request was signed. |
X-Revamp-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256>; a second v1 appears during secret rotation. |
Answer with any 2xx status within 10 seconds. Anything else is a failure and is retried: 6 attempts in total: the first, then retries after 1 min, 5 min, 30 min, 2 h, 12 h. After the last attempt fails the delivery is marked dead and the workspace gets an alert. An endpoint that fails 20 times in a row is paused until you turn it back on.
A retry or a manual redelivery carries the same X-Revamp-Event-Id, so store the ids you have processed and skip repeats.
Verify the signature
The signature header looks like this:
X-Revamp-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
To verify it:
- Read the raw request body before any JSON parsing. Re-serialising changes the bytes.
- Split the header on commas. Take
tand everyv1value. - Reject the request when
tis more than 300 seconds away from your clock. This stops replays. - Compute
HMAC-SHA256(secret, t + "." + rawBody)as lowercase hex. - Compare it with each
v1in constant time. Accept when one matches.
During a secret rotation the header carries two v1 values, one per secret, so receivers that still hold the old secret keep working.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(header, rawBody, secret, toleranceSeconds = 300) {
let t = NaN;
const v1 = [];
for (const part of header.split(",")) {
const [key, value = ""] = part.split("=", 2);
if (key === "t") t = Number(value);
if (key === "v1") v1.push(value);
}
if (!Number.isInteger(t) || v1.length === 0) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
return v1.some((sig) => {
const got = Buffer.from(sig, "hex");
return got.length === expected.length && timingSafeEqual(got, expected);
});
}
Python
import hashlib, hmac, time
def verify(header: str, raw_body: bytes, secret: str, tolerance: int = 300) -> bool:
pairs = [p.split("=", 1) for p in header.split(",")]
t = next((v for k, v in pairs if k == "t"), None)
sigs = [v for k, v in pairs if k == "v1"]
if t is None or not t.isdigit() or not sigs:
return False
if abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in sigs)
PHP
function verify(string $header, string $rawBody, string $secret, int $tolerance = 300): bool {
$t = null; $sigs = [];
foreach (explode(',', $header) as $part) {
[$k, $v] = array_pad(explode('=', $part, 2), 2, '');
if ($k === 't') $t = $v;
if ($k === 'v1') $sigs[] = $v;
}
if (!ctype_digit((string) $t) || !$sigs || abs(time() - (int) $t) > $tolerance) return false;
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
foreach ($sigs as $s) if (hash_equals($expected, $s)) return true;
return false;
}
Zapier, Make and GoHighLevel
These tools cannot verify an HMAC themselves. Give the endpoint a long, unguessable path, keep the URL private, and deduplicate on X-Revamp-Event-Id.