Build on it
Webhooks
Receive DL2App events (clicks, installs, opens, link changes, health alerts) as signed HTTPS requests. Signature format, retries and automatic disabling.
A webhook sends an HTTPS POST to your server when something happens. Create one in the dashboard, choose the events, and optionally limit it to one project, link or tag. Each webhook has its own signing secret, a delivery log, and a button to send a test event.
Events
| Type | Sent when |
|---|---|
link.created | One link was created in the dashboard or through the API. Imports and bulk creates send links.imported instead. |
link.updated | A link's destination, routing, tags or status changed. |
link.archived | A link was archived. It stops redirecting and keeps its slug. |
link.flagged | Abuse review or destination scanning flagged a link, so it no longer redirects. |
links.imported | A CSV import or a bulk API call finished. One event for the whole batch, with counts and the ids of the links it made. |
link.clicked | Someone followed a link. Only people by default; a webhook can opt in to bots and email scanners. Never contains an IP address. |
app.installed | The SDK reported a first open after an install. match says whether, how and how surely we tied it to a click. |
app.opened | The app was opened by a link, either straight away or after an install. |
app.event | Your app reported an event through the SDK, such as a purchase or a sign-up. |
health.failed | Scheduled monitoring found a failing check on a project that was passing before. Sent once, not on every run. |
health.recovered | Every check that was failing passes again. |
domain.verified | A custom link domain or a web domain passed verification. |
alert.created | A new alert appeared on the Alerts page: a health failure, a traffic anomaly, a flagged link, a webhook that was turned off. Alerts about the team as a whole (billing, for example) go to team-wide webhooks only. |
Events that come from link traffic (link.clicked, app.installed, app.opened, app.event) are collected about once a minute, so they arrive shortly after they happen, not instantly. Bulk actions and imports send events too.
The request
POST /your/endpoint
Content-Type: application/json
DL2-Signature: t=1791000000,v1=5f2b…
{
"id": "…",
"type": "link.clicked",
"api_version": "2026-10-01",
"created_at": "2026-10-10T14:03:22.000Z",
"team_id": "…",
"project_id": "…",
"test": false,
"data": { … }
}The payload version changes only for breaking changes; new fields can appear at any time, so ignore fields you do not know.
Verify the signature
The DL2-Signature header holds a Unix timestamp t and one or more v1 values. Each v1 is the HMAC-SHA256, in hex, of <t>.<raw body> using the webhook’s secret. Accept the request if any v1 matches and t is within 5 minutes of now.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const parts = header.split(",").map((p) => p.split("="));
const t = parts.find(([k]) => k === "t")?.[1];
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(t + "." + rawBody).digest();
return parts
.filter(([k]) => k === "v1")
.some(([, v]) => v.length === 64 && timingSafeEqual(Buffer.from(v, "hex"), expected));
}Use the raw request body, before any JSON parsing. When you rotate a secret, deliveries carry a signature for both the old and the new one for 24 hours, so you can switch without dropping events.
Delivery and retries
- Answer with any
2xxstatus within 10 seconds. Do the real work after responding. - A failed delivery is retried with growing gaps, up to 10 attempts over about 24 hours.
- After 20 deliveries in a row have failed, the webhook is switched off and the team is told. Fix the endpoint and switch it back on.
- Deliveries can arrive more than once and out of order. Use the event
idto ignore repeats. - The delivery log keeps 30 days of attempts with the response your server gave, and any delivery can be sent again from there.
- Webhook URLs must be
httpsand may not point at private network addresses.