EaserixDocs
DevelopersAPI referenceLinksAnalytics

Live click feed for one link (SSE)

EAS-83: a Server-Sent Events stream of new clicks on this link as they're recorded. Layered on top of the existing ~500ms click-batch pipeline (never a separate capture path): the connection emits `data: <Click>` for each click as its batch is durably persisted (bot clicks excluded, same as recentClicks), one JSON object per click, plus `: ping` heartbeat comments roughly every 25s. One-way and stateless — any replica can serve any client. Same readable scope as GET /v1/links/{id}: the owner, or anyone in the organization when the link's visibility is "org". EAS-83 follow-up: each Click also carries `clicksUsed`/`qrScansUsed` — the same live Redis usage-cap counts GET /v1/links/{id} returns at the top level — so a client can track the Click/QR-scan-limit stats live instead of only refreshing them on the next full reload. Both are omitted if the usage limiter isn't wired server-side (same best-effort degrade as GET's own clicksUsed/qrScansUsed). Open streams are capped per link and per user on each replica; past a cap the response is 429 with a plain-language message and the client retries on its own. The server ends the stream when the caller's access token expires, so a client reconnects (refreshing its token) and refetches — clicks in the gap are not replayed. Every couple of minutes the server also re-checks that the caller still has access (their identity has not changed, and the link still exists and is readable by them) and ends the stream if not.

View as Markdown
GET
/v1/links/{id}/stream
AuthorizationBearer <token>

In: header

Path Parameters

id*string

Response Body

text/event-stream

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/links/string/stream"
"string"

Live click feed across the caller's own links (SSE)

EAS-83 follow-up: a workspace-scoped Server-Sent Events feed for surfaces that aren't pinned to one link (the Dashboard, Links List, Campaigns, and Analytics pages) — same underlying ~500ms click-batch pipeline as GET /v1/links/{id}/stream, just a second subscription mode over it. Emits `data: {"linkId": "...", "clicks": N, "campaignId": "...", "committedAt": "..."}` — a per-link count for that flush, not enriched per-click detail — as each affected link's batch is durably persisted, plus `: ping` heartbeat comments roughly every 25s. `campaignId` is omitted when the link has none, so a client can attribute a live click to its campaign (the Campaigns list/detail pages) without a second lookup. Scoped to exactly the links the caller owns in their active org (the same population GET /v1/analytics/summary reports on) — a teammate's org-shared link never contributes here, matching that endpoint's existing scope. Open streams are capped per workspace and per user on each replica; past a cap the response is 429 with a plain-language message and the client retries on its own. `committedAt` is when the batch was recorded, to compare with the `asOf` of a read. The server ends the stream when the caller's access token expires, so a client reconnects (refreshing its token) and refetches — clicks in the gap are not replayed. Every couple of minutes the server also re-checks that the caller still has access (their identity has not changed) and ends the stream if not.

Record a conversion for a click

Attributes a conversion to a click. Links with trackConversions enabled append ?esrx_id=<click id> to their destination; the customer's backend captures it and posts it here (typically with an org API key) when the visitor signs up or pays. Clicks are written asynchronously, so an instant conversion can 404 once — retry after a second. Idempotent on clickId+name: retrying the same clickId with the same name (a dropped response, an at-least-once webhook relay) returns 200 with the already-recorded conversion instead of creating a second one. A different name for the same clickId (e.g. "signup", then later "purchase") is a distinct event and is always accepted as a new 201.