Docs/Developers
Webhooks
Webhooks send an HTTP POST to your server when something happens in your project, signed so you can check it came from us. Pro+
Add an endpoint
- Open Webhooks under Developer and click + New webhook.
- Enter your HTTPS endpoint URL and pick the events.
- Copy the signing secret (whsec_…).It's shown once; you need it to verify requests.
https://hooks.yourbrand.com/cloakingurl
✓link.created✓link.updated✓link.deleteddomain.connectedpayment.completed
Create
You can only subscribe to events about things your role can see (for example, billing events need billing access).
Events
| Event | When | data |
|---|---|---|
link.created | A link is created | { id, slug, domain } |
link.updated | A link is changed | { id } |
link.deleted | A link is trashed or deleted | { id } |
conversion.created | A conversion is recorded | { conversion_id, link_id, event, value } |
domain.connected | A domain passes verification | { id, hostname, method } |
subscription.updated | The project's plan changes | { planKey } |
payment.completed | An invoice is paid | { invoiceId, method } |
* | Everything above |
Payload and headers
POST to your endpoint
POST /cloakingurl HTTP/1.1
Content-Type: application/json
User-Agent: CloakingURL-Webhooks/1
X-CloakingURL-Event: link.created
X-CloakingURL-Delivery: 5b1e0c7a-…
X-CloakingURL-Signature: sha256=3f7a9c…
{
"id": "5b1e0c7a-…",
"event": "link.created",
"createdAt": "2026-10-01T08:30:12.000Z",
"data": { "id": "…", "slug": "black-friday", "domain": "go.yourbrand.com" }
}The id (also in X-CloakingURL-Delivery) is the same on every retry of a delivery; use it to ignore duplicates.
Verifying the signature
The signature is an HMAC-SHA256 of the raw request body with your signing secret, hex-encoded. Compute it yourself and compare in constant time. Parse the JSON only after it matches.
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/cloakingurl", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.CLOAKINGURL_WEBHOOK_SECRET)
.update(req.body) // the raw Buffer, not re-serialized JSON
.digest("hex");
const got = req.get("X-CloakingURL-Signature") ?? "";
const ok = got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok) return res.status(401).end();
const { event, data } = JSON.parse(req.body.toString("utf8"));
// … handle the event
res.sendStatus(200);
});PHP
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('CLOAKINGURL_WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_X_CLOAKINGURL_SIGNATURE'] ?? '')) {
http_response_code(401); exit;
}
$payload = json_decode($body, true);Retries and the delivery log
- Reply with any 2xx within 10 seconds. Do slow work after replying.
- Anything else (an error code, a timeout) is retried up to 5 attempts in total, with growing gaps starting at 2 seconds.
- Each webhook has a delivery log showing the last 100 deliveries: event, status, response code, attempts and time.
TipFor local testing, point a webhook at a request inspector (such as webhook.site) or a tunnel to your machine, then create a link to fire
link.created.