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
- It receives a
RedirectTrackingPayloadwith fieldslogEventType,headers,body, andbodyRaw - It validates the payload
logEventTypemust be eitherwebhookorredirect- 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, thex-bank-signatureheader must be present and must pass anHMAC-SHA256check computed over the raw bytes of the body usingREDIRECT_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
SaveTrackingLogconverts the data into thetracking_logtable's shapegetActionType()classifies the action asread,click, orsendhandlePostbackEvent()extracts data from the postback, whilehandleAutoResponseEvent()handles the auto-response logging case- It fills in context fields:
campaign_id,rich_message_id,rich_message_index,line_uid,content_type, andtype(one ofuri,asset,postback, ormessage)
insertTrackingLogwrites the new row intotracking_logsaveTrackingLineUsersupdates theline_userrow'slast_activity_typeandlast_activity_statusfields, and if the user has been silent for over an hour, also updateslast_activity_periodcheckAndCompleteDelayWaitchecks 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 fromtrigger_ruletoworkflow_idandscheduled_action- The INSERT fires a Postgres trigger that sends
pg_notifyon channelcampaign_click, which pg-listener picks up and forwards into thecampaign_click_triggerqueue for the trigger engine to process
Key Files & Functions
internal/tracking/service.goService.ProcessTrackingLog(ctx, payload)— the entry point combining validation and signature checkingverifyXBankSignature(),getActionType(),initializeTrackingData()SaveTrackingLog(),saveTrackingLogInner(),insertTrackingLog(),saveTrackingLineUsers()handlePostbackEvent(),handleAutoResponseEvent(),checkAndCompleteDelayWait()- The constant
moduleNameisTRACKING, used as the Redis key prefix
internal/tracking/consumer.go—Consumer.HandleTrackingLoginternal/tracking/payloads.go—RedirectTrackingPayloadandUnmarshalRedirectTrackingPayloadinternal/tracking/signature_test.go— the signature verification test suitecmd/worker/integration.go—lineUserFinder, an adapter fortracking.LineUserService- Related queue:
tracking_log(runtime profilemain)
Connections to Other Services
- Source of the work —
line-management-webhook-go, both from postbacks and from the redirect service's callback, plus the worker's ownline_webhookqueue handler in the postback event case - Database —
tracking_log(INSERT),line_user(UPDATE of last-activity fields), andtrigger_ruleplusscheduled_actionfor the delay-wait case - Redis — keys prefixed with
TRACKING:, and this is where theCAMPAIGN_STAT_DIRTY:andRICH_MENU_STAT_DIRTY:dirty flags get set for the scanners to pick up later - Environment variable —
REDIRECT_API_SECRETfor verifyingx-bank-signature - Downstream effects — campaign stat calculation, rich menu stat calculation, and the trigger engine's handling of campaign clicks