API reference

Webhooks

We POST a signed JSON body to your endpoint when something happens in your workspace. Add an endpoint in the dashboard under Webhooks, then use the simulator below to see exactly what arrives before you write any receiver code.

Try it against your own URL

Fires a real, signed event at any public address — your own server, a tunnel to your laptop, or a throwaway inbox like webhook.site. The signing secret is generated for this one send and shown to you, so you can verify the signature yourself.

The address has to be publicly reachable. Private and loopback addresses are refused — otherwise this would be a way to make our servers probe networks they have no business in.

Verify every request

Your endpoint URL is a public address. Anyone who learns it can POST to it, so the signature is the only thing separating an event from us and a body somebody made up. Check it before you act on anything.

import crypto from "node:crypto";

export function verify(rawBody: string, header: string, secret: string): boolean {
  // The header looks like: t=1755691200,v1=abc123...
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=") as [string, string])
  );
  const timestamp = Number(parts.t);
  if (!timestamp) return false;

  // Reject anything older than five minutes. Without this, a signature
  // captured once can be replayed forever.
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  // Constant-time. A plain === leaks how many characters matched.
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1 ?? "")
  );
}

Sign the raw body, byte for byte

Parse the JSON after you verify, never before. Re-serialising the body changes the bytes — key order, whitespace, number formatting — and the signature will not match. This is the single most common reason a verifier that looks correct rejects everything.

What to expect

Events

Every payload is wrapped in the same envelope. Switch on event, and read the details from data.

click.receivedHigh volume
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "click.received",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "link": {
      "id": "6f3c1b2a-1111-4222-8333-444455556666",
      "slug": "spring-sale",
      "domain": "ryabi.ls",
      "shortUrl": "https://ryabi.ls/spring-sale",
      "destinationUrl": "https://example.com/spring-sale",
      "title": "Spring sale"
    },
    "click": {
      "id": "9a8b7c6d-2222-4333-8444-555566667777",
      "timestamp": "2026-08-20T12:00:00Z",
      "country": "US",
      "city": "San Francisco",
      "device": "mobile",
      "browser": "Safari",
      "os": "iOS",
      "referrer": "https://example.net/blog",
      "ip": "203.0.113.42",
      "isUnique": true
    }
  }
}
click.milestone
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "click.milestone",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "link": {
      "id": "6f3c1b2a-1111-4222-8333-444455556666",
      "slug": "spring-sale",
      "domain": "ryabi.ls",
      "shortUrl": "https://ryabi.ls/spring-sale",
      "destinationUrl": "https://example.com/spring-sale",
      "title": "Spring sale"
    },
    "milestone": 100000,
    "totalClicks": 100000,
    "reachedAt": "2026-08-20T12:00:00Z"
  }
}
click.spike
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "click.spike",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "link": {
      "id": "6f3c1b2a-1111-4222-8333-444455556666",
      "slug": "spring-sale",
      "domain": "ryabi.ls",
      "shortUrl": "https://ryabi.ls/spring-sale",
      "destinationUrl": "https://example.com/spring-sale",
      "title": "Spring sale"
    },
    "window": "15m",
    "clicksInWindow": 4200,
    "baselinePerWindow": 60,
    "multiplier": 70,
    "detectedAt": "2026-08-20T12:00:00Z"
  }
}
link.created
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "link.created",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "link": {
      "id": "6f3c1b2a-1111-4222-8333-444455556666",
      "slug": "spring-sale",
      "domain": "ryabi.ls",
      "shortUrl": "https://ryabi.ls/spring-sale",
      "destinationUrl": "https://example.com/spring-sale",
      "title": "Spring sale"
    },
    "createdAt": "2026-08-20T12:00:00Z"
  }
}
link.updated
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "link.updated",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "link": {
      "id": "6f3c1b2a-1111-4222-8333-444455556666",
      "slug": "spring-sale",
      "domain": "ryabi.ls",
      "shortUrl": "https://ryabi.ls/spring-sale",
      "destinationUrl": "https://example.com/spring-sale",
      "title": "Spring sale"
    },
    "changed": [
      "destinationUrl",
      "title"
    ],
    "updatedAt": "2026-08-20T12:00:00Z"
  }
}
link.deleted
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "link.deleted",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "link": {
      "id": "6f3c1b2a-1111-4222-8333-444455556666",
      "slug": "spring-sale",
      "domain": "ryabi.ls"
    },
    "deletedAt": "2026-08-20T12:00:00Z"
  }
}
lead.created
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "lead.created",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "lead": {
      "id": "1a2b3c4d-3333-4444-8555-666677778888",
      "email": "someone@example.com",
      "name": "Alex Doe",
      "source": "bridge_page",
      "createdAt": "2026-08-20T12:00:00Z"
    },
    "page": {
      "id": "2b3c4d5e-4444-4555-8666-777788889999",
      "slug": "launch"
    }
  }
}
page.viewedHigh volume
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "page.viewed",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "page": {
      "id": "2b3c4d5e-4444-4555-8666-777788889999",
      "slug": "launch",
      "title": "Launch"
    },
    "view": {
      "timestamp": "2026-08-20T12:00:00Z",
      "country": "GB",
      "device": "desktop",
      "referrer": "https://example.net/"
    }
  }
}
page.clickHigh volume
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "page.click",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "page": {
      "id": "2b3c4d5e-4444-4555-8666-777788889999",
      "slug": "launch"
    },
    "block": {
      "id": "cta-1",
      "type": "button",
      "label": "Buy now"
    },
    "timestamp": "2026-08-20T12:00:00Z",
    "country": "GB"
  }
}
sale.completed
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "sale.completed",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "sale": {
      "id": "3c4d5e6f-5555-4666-8777-888899990000",
      "kind": "digital_product",
      "amountCents": 4900,
      "currency": "USD",
      "buyerEmail": "someone@example.com",
      "completedAt": "2026-08-20T12:00:00Z"
    },
    "page": {
      "id": "2b3c4d5e-4444-4555-8666-777788889999",
      "slug": "launch"
    }
  }
}
assistant.intent
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "assistant.intent",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "intent": "pricing_question",
    "confidence": 0.87,
    "question": "How much is the business plan?",
    "answered": true,
    "conversationId": "4d5e6f70-6666-4777-8888-999900001111",
    "timestamp": "2026-08-20T12:00:00Z"
  }
}
rss.item.created
{
  "id": "evt_7f8a9b0c1d2e3f40",
  "event": "rss.item.created",
  "createdAt": "2026-08-20T12:00:00Z",
  "workspaceId": "8f9a0b1c-8888-4999-8000-111122223333",
  "attempt": 1,
  "data": {
    "source": {
      "id": "5e6f7081-7777-4888-8999-000011112222",
      "title": "Company blog"
    },
    "item": {
      "title": "Introducing the spring sale",
      "url": "https://example.com/blog/spring-sale",
      "publishedAt": "2026-08-20T11:30:00Z"
    },
    "link": {
      "id": "6f3c1b2a-1111-4222-8333-444455556666",
      "slug": "spring-sale",
      "domain": "ryabi.ls",
      "shortUrl": "https://ryabi.ls/spring-sale",
      "destinationUrl": "https://example.com/spring-sale",
      "title": "Spring sale"
    }
  }
}