Integrations
Webhooks: subscribe to link and click events
Configure an HTTP endpoint to receive real-time events from Elido — link created, link clicked (aggregated), domain verified, abuse flagged, and more.
Updated 2026-05-15
Webhooks push events from Elido to your server as they happen. Use them instead of polling the API when you need real-time data — typical use cases include syncing link metadata to your CRM, building a custom dashboard, or kicking off a CI pipeline when a link is published.
Add a webhook endpoint#
- Dashboard → Webhooks → New endpoint.
- Paste the URL we should POST to. It must be HTTPS — we reject plain HTTP at create time.
- Pick the event kinds you want. The full list is below.
- (Optional) Paste a signing secret, or let us generate one. The secret is shown once; copy it before leaving the page.
- Save. We send a test event immediately so you can confirm the endpoint is reachable.
Event catalogue#
The current event kinds. Names follow <resource>.<action> and stay stable across versions.
link.created— a new short link was created.link.updated— destination, slug, expiry, or password changed.link.deleted— link was soft-deleted (still recoverable for 30 days).link.clicked.aggregated— every 60 seconds we send the click counts per link for the previous window. We don't deliver per-click events over webhooks (the volume would crush most endpoints) — use the click export for raw event data.domain.verified— a custom domain's DNS check passed.domain.tls_renewed— Caddy renewed the TLS cert for a custom domain.qr.generated— a QR code SVG/PNG was rendered.abuse.flagged— our URL scanner marked a destination as suspicious.member.invited/member.removed— workspace membership changes.
Payload shape#
Every event has the same envelope:
{
"id": "evt_2c8L9N4M5",
"type": "link.created",
"created_at": "2026-05-15T09:42:11.382Z",
"workspace_id": 4123,
"data": {
"link_id": 891234,
"slug": "spring-2026",
"destination": "https://acme.com/spring-sale",
"created_by": "user_5821"
}
}
id is unique per delivery. Use it for idempotency — if you receive the same id twice (because we retried), skip the duplicate.
Verifying signatures#
We sign every webhook body with HMAC-SHA256 using your endpoint's secret. The signature is in the Elido-Signature header as t=<unix_ts>,v1=<hex>.
To verify in Node:
import { createHmac } from "node:crypto";
function verify(secret: string, body: string, header: string): boolean {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=")),
);
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${body}`)
.digest("hex");
return expected === parts.v1;
}
We include the timestamp in the signed payload to prevent replays. Reject events where |now - t| > 300 seconds.
Retries#
We retry failed deliveries (any non-2xx response, or no response within 10 seconds) with exponential backoff: 30s, 1m, 5m, 30m, 2h, 12h. After 6 failures we mark the endpoint failing and stop retrying that specific event. The endpoint stays subscribed; new events continue to attempt delivery.
Endpoint stays failing for 5 consecutive deliveries → we auto-disable it and email the workspace owner. Re-enable in the dashboard once you've fixed your server.
Delivery log#
Open any webhook in the dashboard to see the last 500 deliveries, with status code, response time, and the request/response body for each. Failed deliveries can be replayed manually from this view.
Local testing#
Use the Elido CLI to forward webhooks to localhost:
npx @elido/cli webhooks forward --url http://localhost:3000/webhooks
The CLI registers a temporary endpoint that lasts until you Ctrl-C, tunnels deliveries to your machine, and prints the request body for each event.
Limits#
- 20 endpoints per workspace (Pro), 100 (Business).
- 10 KB max signed payload size. Larger payloads are split —
dataincludes atruncated: trueflag and a URL to fetch the full body. - 50 events per second sustained per endpoint. Bursts are queued.
Troubleshooting#
Endpoint reports 401 in the delivery log. Your endpoint is verifying signatures with the wrong secret. Compare the secret in Webhooks → Endpoint → Settings against the one your server has stored.
Some events are arriving twice. Either we retried after a slow response, or your endpoint timed out without responding. Use the event id for idempotency.
link.clicked.aggregated counts don't match analytics dashboard. The dashboard is real-time (within 30s of click); the webhook is windowed at 60s. There's also a small (~1%) discrepancy because bots filtered out of the dashboard are still included in raw aggregates until the bot-filter window closes.