Skip to main content

Tracking Log Recording

Overview

Every read, click, and message send in the system ultimately gets recorded into a single tracking_log table. That table is the source of truth for campaign reports and rich menu stats, and it's also what fires campaign_click-type triggers through a Postgres trigger working together with pg-listener.

This consumer accepts events from two sources: webhook, which is a postback coming from LINE, and redirect, which happens when a user taps a link through the redirect service. The latter must pass HMAC signature verification before it can be recorded.

Business Flow

  1. It receives a RedirectTrackingPayload with fields logEventType, headers, body, and bodyRaw
  2. It validates the payload
    • logEventType must be either webhook or redirect
    • For webhook, a user id must be present in the first event object of the body and must not be empty; if the shape deviates from the standard, it logs a warning but keeps going
    • For redirect, the x-bank-signature header must be present and must pass an HMAC-SHA256 check computed over the raw bytes of the body using REDIRECT_API_SECRET, compared in a timing-safe way. Raw bytes are required because re-marshaling in Go reorders keys and escapes HTML characters, which would break the signature
  3. SaveTrackingLog converts the data into the tracking_log table's shape
    • getActionType() classifies the action as read, click, or send
    • handlePostbackEvent() extracts data from the postback, while handleAutoResponseEvent() handles the auto-response logging case
    • It fills in context fields: campaign_id, rich_message_id, rich_message_index, line_uid, content_type, and type (one of uri, asset, postback, or message)
  4. insertTrackingLog writes the new row into tracking_log
  5. saveTrackingLineUsers updates the line_user row's last_activity_type and last_activity_status fields, and if the user has been silent for over an hour, also updates last_activity_period
  6. checkAndCompleteDelayWait checks whether this user has a scheduled action set to wait for a click (waitForTracking); if so, this click immediately unblocks that action, checked via a join from trigger_rule to workflow_id and scheduled_action
  7. The INSERT fires a Postgres trigger that sends pg_notify on channel campaign_click, which pg-listener picks up and forwards into the campaign_click_trigger queue for the trigger engine to process

Key Files & Functions

  • internal/tracking/service.go
    • Service.ProcessTrackingLog(ctx, payload) — the entry point combining validation and signature checking
    • verifyXBankSignature(), getActionType(), initializeTrackingData()
    • SaveTrackingLog(), saveTrackingLogInner(), insertTrackingLog(), saveTrackingLineUsers()
    • handlePostbackEvent(), handleAutoResponseEvent(), checkAndCompleteDelayWait()
    • The constant moduleName is TRACKING, used as the Redis key prefix
  • internal/tracking/consumer.goConsumer.HandleTrackingLog
  • internal/tracking/payloads.goRedirectTrackingPayload and UnmarshalRedirectTrackingPayload
  • internal/tracking/signature_test.go — the signature verification test suite
  • cmd/worker/integration.golineUserFinder, an adapter for tracking.LineUserService
  • Related queue: tracking_log (runtime profile main)

Connections to Other Services

  • Source of the workline-management-webhook-go, both from postbacks and from the redirect service's callback, plus the worker's own line_webhook queue handler in the postback event case
  • Databasetracking_log (INSERT), line_user (UPDATE of last-activity fields), and trigger_rule plus scheduled_action for the delay-wait case
  • Redis — keys prefixed with TRACKING:, and this is where the CAMPAIGN_STAT_DIRTY: and RICH_MENU_STAT_DIRTY: dirty flags get set for the scanners to pick up later
  • Environment variableREDIRECT_API_SECRET for verifying x-bank-signature
  • Downstream effects — campaign stat calculation, rich menu stat calculation, and the trigger engine's handling of campaign clicks