Tracking Links (Tracking Token / Redirect)
Overview
When a user taps a link the platform sent them, the system needs to record who tapped which link and from where before forwarding them to the destination. This module owns the link-creation side; the actual redirect happens in client-api and client-web.
There are two mechanisms, chosen by content type:
| Content type | Mechanism | Owner |
|---|---|---|
| Rich Menu | Token-based via TrackingTokenService | cms-api (this module) |
| Lead Generation Form | Token-based | cms-api (this module) |
| Campaign | External RedirectService | worker |
The internal/modules/tracking module exposes no HTTP routes at all, mirroring the
upstream NestJS module which had no controller. It acts purely as a provider, exporting
TrackingTokenService for the LINE Message API and Rich Menu modules to call.
Business Flow
-
A user creates a rich menu with a link button, and
richmenu/tracking_adapter.gocallsTrackingTokenService. -
The service generates a self-contained token and records it in the
tracking_tokentable. -
The operating mode is selected by the
TRACKING_REDIRECT_MODEenvironment variable:builtinuses our own encrypted token implementation ininternal/core/trackinglink- anything else, defaulting to
legacy, uses the original redirect service ininternal/externals/redirects
-
Token structure in builtin mode, defined in
internal/core/trackinglink/token.go:bytes = keyId(1) ‖ nonce(12) ‖ ciphertext ‖ tag(16) // AES-256-GCM, AAD "tracking-link-v1"token = base64url_nopad(bytes)The payload carries a
ctfield set to either"rich_menu"or"lead_gen", with a fixed key order so the byte-level output stays stable. -
The link embedded in the rich menu is therefore a URL containing this token.
-
When the user taps it, client-api decrypts the token, records an event in
tracking_line_users, and redirects to the destination. -
Results are reviewed through the Tracking LINE Users module and the rich menu report pages.
The encoder in cms-api and the decoder in client-api are pinned against the same set of reference tokens. If either side drifts, every link already sent out stops working.
Key Files & Functions
| File | Role |
|---|---|
internal/modules/tracking/service.go | TrackingTokenService — the port of tracking-token.service.ts |
internal/modules/tracking/repository.go | Reads and writes the tracking_token table |
internal/modules/tracking/controller.go | RegisterRoutes is a no-op, registering nothing |
internal/core/trackinglink/token.go | Builds the AES-256-GCM token for builtin mode |
internal/core/trackinglink/config.go | Mode() and IsBuiltin() read TRACKING_REDIRECT_MODE |
internal/externals/redirects/redirects.go | Client for the original redirect service (legacy mode) |
internal/modules/richmenu/tracking_adapter.go | Where the rich menu module calls in |
Endpoints — none; this module registers no routes.
Connections to Other Services
- Permissions — with no routes there are no guards; access control happens in the calling modules.
- Tables —
tracking_token,tracking_line_usersandcontent_page_utm. - Environment —
TRACKING_REDIRECT_MODEselects betweenbuiltinandlegacy; the AES key is referenced by thekeyIdembedded in each token. - Cross-service — line-management-client-api-go decrypts the tokens, and the worker uses RedirectService for campaign links.
- Design spec —
docs/superpowers/specs/2026-07-06-builtin-tracking-redirect-design.md - Related modules — Rich Menu, LINE Message API, Tracking LINE Users and Campaign Management.