Webhooks
HTTPS endpoints for link.visit, link.download, and link.play, delivered by Svix.
HTTPS endpoints. HTTPS only. No localhost or private IPs. Up to 10 per account.
Events: link.visit, link.download, link.play. Secret is whsec_…, shown once on create/rotate. Keep it. Never put it in the JSON body.
CLI
linkdash webhooks create --url https://example.com/hooks
linkdash webhooks list
linkdash webhooks get <id>
linkdash webhooks rotate <id>
linkdash webhooks delete <id>
list and get return secretHint only (whsec_…xxxx).
HTTP
curl https://api.linkdash.dev/v1/webhooks \
-X POST \
-H "Authorization: Bearer ldash_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hooks"}'
GET /webhooks, POST /webhooks, GET /webhooks/:id, DELETE /webhooks/:id, POST /webhooks/:id/rotate.
Payload
{
"type": "link.visit",
"timestamp": "2026-08-17T16:00:00.000Z",
"data": { "id": "deck", "kind": "file", "country": "DE", "referrer": "…" }
}
Headers on every delivery (Svix retries reuse the same id):
svix-idsvix-timestamp(unix seconds)svix-signature(v1,<base64 hmac>)
Signed content: {id}.{timestamp}.{raw body} HMAC-SHA256. Secret format whsec_ + base64.
Verify
Deliveries are sent by Svix and follow the Standard Webhooks spec. Verify with the svix package (same as Clerk and Resend).
npm i svix
import { Webhook, WebhookVerificationError } from "svix";
const wh = new Webhook(process.env.LINKDASH_WEBHOOK_SECRET); // whsec_… shown once
try {
const event = wh.verify(rawBody, {
"svix-id": headers["svix-id"],
"svix-timestamp": headers["svix-timestamp"],
"svix-signature": headers["svix-signature"],
});
// event.type is "link.visit" | "link.download" | "link.play"
} catch (err) {
if (err instanceof WebhookVerificationError) {
// 400 — do not process
}
throw err;
}
