Conversion Tracking Events

Purpose and Scope

Conversion tracking in Dub connects a visitor's click journey to downstream outcomes such as signups, purchases, and application opens. For developers, the important distinction is that Dub exposes dedicated tracking endpoints for sending conversion signals, and a separate events surface for reading back chronological activity in the workspace. The repository evidence shows this split clearly: trackPaths registers write-oriented POST endpoints for lead, sale, and open tracking, while eventsPath registers a read-oriented GET /events endpoint that returns workspace events through the OpenAPI contract.

Sources: apps/web/lib/openapi/track/index.ts, apps/web/lib/openapi/events/index.ts

The product documentation frames conversion tracking as the way to understand how link clicks convert to signups and sales. That reader-facing model maps to the source model without requiring every conversion type to be a single generic API route. Leads and sales are explicit tracking primitives, opens are also accepted through the Track API, and the Events API provides the stream-oriented view over recorded activity. In practice, teams instrument the Track endpoints in their application or backend, then inspect the resulting events in the analytics interface or through GET /events.

Sources: apps/web/lib/openapi/track/index.ts, apps/web/lib/openapi/events/index.ts, apps/web/ui/analytics/events/index.tsx

Core Primitives

A lead represents a non-monetary conversion such as a signup, form submission, account creation, or qualified prospect. In the OpenAPI path map, Dub exposes lead tracking at POST /track/lead, with the operation implementation imported as trackLead. The page-level takeaway is that lead tracking is a first-class conversion write path rather than just an analytics filter. When an application wants Dub to connect a user action back to an attributed short link or partner journey, the lead endpoint is the explicit API surface for recording that milestone.

Sources: apps/web/lib/openapi/track/index.ts

A sale represents a monetary conversion. Dub exposes sale tracking at POST /track/sale, with the operation implementation imported as trackSale. Official docs also describe sale-related workflows such as refunds and partner commission status updates, which is why sales sit at the center of affiliate-program reporting. The supplied OpenAPI index does not enumerate body fields, but it does establish that sale tracking is a separate endpoint from lead tracking. That separation matters because downstream reporting and commission logic can treat revenue-bearing conversions differently from signup-style conversions.

Sources: apps/web/lib/openapi/track/index.ts

An open represents a deep-link or app-open conversion signal. Dub exposes this through POST /track/open, with the operation implementation imported as trackOpen. This is useful when the conversion you care about is not a completed checkout or a submitted lead form, but evidence that a user opened an application or destination experience after a Dub link interaction. Keeping opens in the Track API alongside leads and sales gives integrators one conceptual family for sending conversion signals, even though each endpoint can enforce its own operation contract.

Sources: apps/web/lib/openapi/track/index.ts

An event is the read-side representation of recorded activity. The Events OpenAPI module defines listEvents with operation id listEvents, a Speakeasy name override of list, and the summary List all events. Its response is an array discriminated by the event field, currently composed from click, lead, and sale response schemas. That means event listing is broader than conversion writes because it includes click activity, while also being narrower than the Track path map in the visible source because the listed response union names clicks, leads, and sales.

Sources: apps/web/lib/openapi/events/index.ts

Relevant Source Files

  • apps/web/lib/openapi/track/index.ts — Registers the public Track API paths for lead, sale, and open conversion writes under trackPaths.
  • apps/web/lib/openapi/events/index.ts — Defines the Events API list operation, including query parameters, response schema union, authentication requirement, tags, and /events path registration.
  • apps/web/ui/analytics/events/index.tsx — Implements the analytics Events page container, wiring the analytics provider, events tabs, events table, plan gating, and upgrade overlay for the real-time Events Stream UI.

System-to-Code Mapping

The Track API file is intentionally small because it acts as a path-level index rather than the business logic for each conversion type. It imports trackLead, trackSale, and trackOpen, then attaches them to three path entries under trackPaths. That pattern is important for OpenAPI consumers: it means the public documentation and generated SDK surfaces can discover all tracking write endpoints from one path object, while the detailed operation schemas remain modularized in neighboring files. For readers navigating the codebase, start with this index to understand the available conversion write routes.

Sources: apps/web/lib/openapi/track/index.ts

The Events API file is more descriptive at the operation level. listEvents is declared as a ZodOpenApiOperationObject, tagged as Events, protected with token security, and described as retrieving a paginated list of events for the authenticated workspace. Its query parameters come from eventsQuerySchema, and its success response is a JSON array. The response item schema is a discriminated union over event, allowing consumers to distinguish click, lead, and sale records while still requesting them from one endpoint.

Sources: apps/web/lib/openapi/events/index.ts

The analytics UI file shows how the event concepts surface in the dashboard. AnalyticsEvents wraps the page in AnalyticsProvider, renders AnalyticsToggle with page="events", then places EventsTabs and an EventsTableContainer inside the dashboard layout. The table container reads the selected event tab from AnalyticsContext and workspace plan data from useWorkspace. This establishes the UI contract: events are displayed as a tabbed analytics view, scoped to the active workspace, and rendered by an EventsTable component.

Sources: apps/web/ui/analytics/events/index.tsx

API Components Reference

ComponentPublic surfaceDirectionSource-backed behavior
trackPathsPOST /track/leadWriteRegisters lead conversion tracking with trackLead.
trackPathsPOST /track/saleWriteRegisters sale conversion tracking with trackSale.
trackPathsPOST /track/openWriteRegisters open tracking with trackOpen.
listEventsGET /eventsReadRetrieves a paginated list of events for the authenticated workspace.
eventsPath/eventsReadAttaches listEvents to the Events API path map.
AnalyticsEventsDashboard Events pageRead UIRenders the events view under the analytics provider with tabs and table UI.

The key OpenAPI contract for reading events is compact but precise. The operation id is listEvents, the generated Speakeasy name override is list, and the tag is Events. Authentication is declared with security: [{ token: [] }], so the endpoint is part of the authenticated API surface rather than a public ingestion route. On success, 200 returns application/json containing an array of event objects. The array items are discriminated by the event property, which is the mechanism clients can use to branch between click, lead, and sale response shapes.

Sources: apps/web/lib/openapi/events/index.ts

For write operations, the visible Track API contract is path-oriented. The registered endpoints are POST /track/lead, POST /track/sale, and POST /track/open. Because the index imports operation objects from ./lead, ./sale, and ./open, each conversion type can evolve its own request and response schema while staying discoverable through a shared trackPaths export. SDK generators and documentation builders can use that export as the authoritative Track family path map without conflating all conversion types into one generic endpoint.

Sources: apps/web/lib/openapi/track/index.ts

Dashboard and Events Stream Flow

The dashboard events surface is designed as an analytics view rather than a standalone utility. AnalyticsEvents receives optional staticDomain, staticUrl, and adminPage props, then passes those through to AnalyticsProvider. That allows the same event-viewing component to work in different analytics contexts while preserving the shared provider state. The component also renders AnalyticsToggle with the events page selected, so users can move between analytics summaries and the event stream without leaving the analytics workflow.

Sources: apps/web/ui/analytics/events/index.tsx

Access control in the UI is plan-aware. EventsTableContainer reads plan and slug from useWorkspace, marks the table as requiring an upgrade when the plan is free or pro, and supplies an EmptyState upgrade overlay titled Real-time Events Stream. The overlay text changes based on the selected tab: for clicks it mentions clicks & QR code scans, while other tabs are named directly. This is consistent with the official docs positioning of the real-time events stream as a Business-plan-and-above feature.

Sources: apps/web/ui/analytics/events/index.tsx

The dashboard implementation also clarifies how events are explored. EventsTabs sits above EventsTableContainer, and the table is keyed by selectedTab. Re-keying the table on tab change forces the table subtree to reset for the selected event category, which is a practical UI detail for real-time or paginated event views. The OpenAPI read endpoint provides the programmatic listing surface, while the dashboard wraps that idea in workspace context, tab selection, upgrade handling, and user-facing explanation.

Sources: apps/web/lib/openapi/events/index.ts, apps/web/ui/analytics/events/index.tsx

Implementation Details and Constraints

When integrating conversion tracking, treat write and read paths as separate phases. First, instrument the conversion event in the system that knows the outcome: application signup code for leads, billing or checkout code for sales, and app/deep-link handling code for opens. Second, use Dub analytics or the Events API to inspect recorded activity. This separation keeps ingestion endpoints focused on accepting conversion signals, while listing and dashboard components can apply filters, pagination, authentication, plan checks, and event-specific rendering.

Sources: apps/web/lib/openapi/track/index.ts, apps/web/lib/openapi/events/index.ts, apps/web/ui/analytics/events/index.tsx

The discriminated union in the Events API is a particularly useful client contract. Rather than forcing API consumers to infer the type of a returned item from optional fields, the schema declares a discriminator named event. That makes generated clients and manual integrations safer because each list item can be switched on its event kind before accessing click, lead, or sale fields. The source-backed response union also reminds implementers not to assume every Track write primitive necessarily appears in the visible Events list response in the same form.

Sources: apps/web/lib/openapi/events/index.ts

Next Steps

Use this page as the conceptual bridge between conversion instrumentation and event inspection. If you are adding tracking to an application, start with the Track API family and choose the endpoint that matches the outcome: lead, sale, or open. If you are building reporting, exports, or a dashboard integration, start with GET /events and handle the discriminated click, lead, and sale event records. For UI work, follow the AnalyticsEvents container to understand workspace scoping, tab state, and upgrade behavior before changing lower-level table rendering.

Sources: apps/web/lib/openapi/track/index.ts, apps/web/lib/openapi/events/index.ts, apps/web/ui/analytics/events/index.tsx