Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là PM của một ứng dụng fintech tại Việt Nam. Buổi sáng, người dùng mở app trên iPhone để kiểm tra số dư. Trưa, họ lên website công ty tra cứu lịch sử giao dịch trong lúc làm việc. Tối, họ dùng app Android trên chiếc điện thoại phụ để chuyển tiền. Với con người, đây rõ ràng là một khách hàng duy nhất trải qua một hành trình liền mạch. Nhưng với hệ thống analytics mặc định của bạn, đây là ba người dùng hoàn toàn khác nhau, mỗi người có một anonymous ID riêng, không hề liên quan.
Đây chính là bài toán trung tâm của bài học hôm nay: cross-platform tracking — theo dõi một người dùng xuyên suốt nhiều nền tảng (web, iOS, Android, thậm chí server-side) và ghép họ lại thành một identity thống nhất.
Vì sao điều này quan trọng đến vậy? Vì gần như mọi phân tích bạn học ở các bài trước — funnel, retention, cohort, LTV — đều được tính dựa trên đơn vị "user". Nếu một người bị đếm thành ba, mọi con số của bạn đều sai lệch một cách có hệ thống: retention bị thổi phồng ảo (vì mỗi thiết bị trông như một user mới quay lại), funnel bị đứt gãy (vì bước 1 xảy ra trên web nhưng bước 2 trên app không được ghép nối), và LTV bị chia nhỏ đến mức vô nghĩa. Với các sản phẩm đa nền tảng — mà ở Việt Nam gần như 100% sản phẩm số đều vừa có web vừa có mobile — đây không phải chi tiết kỹ thuật phụ, mà là nền móng quyết định độ tin cậy của toàn bộ dữ liệu.
Khái niệm cốt lõi
Anonymous ID và Identified ID
Mọi công cụ analytics (Mixpanel, Amplitude, PostHog...) đều gán cho mỗi thiết bị/trình duyệt một anonymous ID (còn gọi là device ID hoặc distinct ID mặc định) ngay khi người dùng lần đầu chạm vào sản phẩm — trước cả khi họ đăng ký hay đăng nhập. Anonymous ID gắn với thiết bị, không gắn với con người.
Khi người dùng đăng nhập, bạn có được một định danh thật — thường là user_id từ hệ thống backend của bạn (ví dụ ID trong database). Nhiệm vụ của cross-platform tracking là: mỗi khi biết được user_id thật, hãy gọi lệnh identify để "hàn" anonymous ID của thiết bị đó vào chung một identity với user_id.
Alias, Identify và Identity Resolution
Cơ chế ghép nối này thường được gọi là identity resolution (giải quyết định danh). Có ba khái niệm bạn cần phân biệt rõ:
identify(user_id): nói với công cụ "thiết bị này thuộc về user_id X". Từ giờ mọi event trên thiết bị này gán cho X.alias: tạo liên kết một-chiều giữa anonymous ID và user_id lần đầu tiên (chủ yếu dùng trong Mixpanel legacy, khi user đăng ký lần đầu). Alias không thể đảo ngược, nên phải cẩn thận.- Merge: khi công cụ nhận ra hai identity thực chất là một, nó gộp lịch sử event lại. Amplitude và Mixpanel (ID Merge mới) làm việc này khá tự động; PostHog cũng có cơ chế merge person.
user_id phải là cùng một giá trị trên mọi nền tảng. Nếu web dùng email làm user_id còn app dùng số ID trong database, bạn sẽ không bao giờ ghép được. Đây là lỗi phổ biến nhất mà chúng ta sẽ nói kỹ ở phần sau.Vòng đời của một identity
Hãy hình dung vòng đời chuẩn:
- User lần đầu vào web → nhận anonymous ID
A1. Xem trang chủ, xem sản phẩm (các event này gán choA1). - User đăng ký trên web → bạn gọi
identify("user_123"). GiờA1vàuser_123là một. Toàn bộ lịch sử duyệt trước đó củaA1được "kế thừa" vềuser_123. - Vài ngày sau, cùng người đó tải app iOS, mở lên → nhận anonymous ID
A2. Duyệt vài màn hình (gán choA2). - User đăng nhập app bằng tài khoản cũ → bạn gọi
identify("user_123")trên iOS. GiờA2cũng thuộcuser_123. - Kết quả: một identity
user_123với lịch sử đầy đủ từ cả web lẫn iOS, kể cả phần duyệt ẩn danh trước khi đăng nhập.
Sự khác biệt giữa Mixpanel và Amplitude
Hai công cụ chủ đạo của khóa học xử lý identity theo triết lý hơi khác:
- Amplitude dùng mô hình Device ID + User ID. Mỗi event luôn mang cả hai. Amplitude tự động merge các device ID có chung user ID. Điều này khiến Amplitude khá "khoan dung" — bạn chỉ cần đảm bảo set đúng
user_idkhi login là gần như xong. - Mixpanel với ID Merge (bật trong project settings) hoạt động tương tự: khi bạn
identify, nó liên kết$device:ID vớiuser_id. Với các project cũ dùng cơ chế alias thì bạn phải cẩn thận hơn để tránh gọi alias nhiều lần.
Tình huống thực tế
Ví dụ 1 — Sàn TMĐT giả định "ChợViệt": retention bị thổi phồng 40%
ChợViệt là một sàn thương mại điện tử với cả web và app. Ban đầu, team growth báo cáo Day-7 retention đạt 55% — con số đẹp đến mức đáng ngờ. Khi PM đào sâu, họ phát hiện hệ thống không hề ghép identity giữa web và app. Nhiều khách hàng khám phá sản phẩm trên app di động (nhận anonymous ID app), rồi tối về nhà mở laptop hoàn tất thanh toán trên web (nhận anonymous ID web mới toanh). Công cụ đếm lần quay lại trên web như một "user mới" thay vì "user cũ quay lại".
Khi ChợViệt triển khai unified user ID và merge lại, con số retention thật rơi xuống 39% — thấp hơn nhưng chính xác. Bài học: một retention cao bất thường trong sản phẩm đa nền tảng thường là dấu hiệu của identity bị vỡ, không phải sản phẩm tốt. Con số xấu mà đúng còn hơn con số đẹp mà sai, vì quyết định dựa trên số sai sẽ đốt tiền ở sai chỗ.
Ví dụ 2 — Ứng dụng gọi xe khu vực Đông Nam Á: funnel đặt chuyến bị đứt
Một ứng dụng gọi xe (tương tự mô hình Grab/Gojek) chạy chiến dịch marketing dẫn traffic về landing page web để giới thiệu ưu đãi. User đọc landing page (event promo_viewed trên web, anonymous ID web), bấm "Tải app", cài app, rồi đặt chuyến đầu tiên (event ride_booked trên app, anonymous ID app).
Team marketing muốn đo funnel promo_viewed → app_installed → ride_booked, nhưng vì web và app không ghép được, funnel luôn hiển thị conversion 0% từ bước web sang bước app — nhìn như chiến dịch thất bại hoàn toàn. Thực tế chiến dịch rất hiệu quả, chỉ là dữ liệu không nói chuyện được với nhau.
Giải pháp họ dùng là deferred deep link (ví dụ AppsFlyer, Branch): khi user bấm "Tải app" trên web, một token định danh được lưu và truyền vào app sau khi cài đặt, cho phép nối anonymous ID web với session app đầu tiên. Sau khi triển khai, funnel cho thấy conversion web-to-ride thực tế là 12% — đủ cơ sở để tăng ngân sách. Bài học: với luồng web-to-app-install, chỉ identify là chưa đủ (vì lúc đó user chưa đăng nhập trên app); bạn cần công cụ attribution/deep-link để bắc cầu.
Ví dụ 3 — App fintech: user bị nhân đôi vì user_id không nhất quán
Một startup fintech triển khai tracking trên cả ba nền tảng. Nhưng team web dùng email làm user_id gọi identify, còn team mobile dùng UUID từ database. Kết quả: cùng một khách hàng tồn tại thành hai identity không bao giờ gặp nhau. Báo cáo ARPU bị chia đôi một cách bí ẩn, và cohort "user đã KYC" bị thiếu một nửa số người thực tế đã KYC trên web.
Họ phải chạy một đợt sửa: thống nhất user_id = UUID database trên mọi nền tảng, và viết một job backfill để merge các identity trùng qua email. Bài học: quyết định chọn khóa định danh (identity key) phải là quyết định chung ở tầng công ty, chốt trong tracking plan, trước khi bất kỳ team nào code. Sửa sau khi dữ liệu đã lệch tốn gấp nhiều lần làm đúng từ đầu.
Hướng dẫn từng bước
Bước 1 — Chọn một identity key duy nhất cho toàn công ty. Thường là user_id (UUID hoặc primary key trong database backend). Tuyệt đối không dùng email, số điện thoại hay username — chúng có thể thay đổi. Ghi rõ trong tracking plan: "identity key = database user_id, kiểu string, dùng chung trên web/iOS/Android/server."
Bước 2 — Để công cụ tự sinh anonymous ID. Khi khởi tạo SDK trên mỗi nền tảng, đừng tự đặt distinct ID. Hãy để Mixpanel/Amplitude tự sinh anonymous ID cho thiết bị. Việc của bạn chỉ là bắt đúng thời điểm để identify.
Bước 3 — Gọi identify(user_id) ngay sau khi login/register thành công. Đặt lệnh này ở đúng một chỗ trong code — lý tưởng là trong hàm xử lý callback đăng nhập/đăng ký, ngay sau khi backend trả về user_id hợp lệ. Ví dụ với Mixpanel: mixpanel.identify("user_123"); với Amplitude: amplitude.setUserId("user_123"). Làm điều này trên cả ba nền tảng với cùng logic.
Bước 4 — Set user properties (People Properties) sau identify. Ngay sau identify, cập nhật các thuộc tính người dùng (tên gói, ngày đăng ký, thành phố...) qua people.set / identify với Identify object. Điều này đảm bảo profile thống nhất bất kể user đăng nhập từ thiết bị nào. (Chi tiết về People Properties đã học ở Bài 12, ở đây chỉ nhấn mạnh nó phải chạy sau identify.)
Bước 5 — Xử lý logout đúng cách. Khi user đăng xuất, gọi reset() (Mixpanel/Amplitude đều có). Lệnh này cắt liên kết thiết bị khỏi user cũ và sinh anonymous ID mới, tránh trường hợp trên thiết bị dùng chung, hành vi của người sau bị gán nhầm cho người trước. Đây là lỗi bị bỏ quên rất thường xuyên.
Bước 6 — Bắc cầu cho luồng web-to-app (nếu có). Nếu bạn có chiến dịch dẫn từ web sang cài app, tích hợp một công cụ deferred deep link (Branch, AppsFlyer) để truyền định danh qua giai đoạn cài đặt. Chỉ dựa vào identify sẽ mất đứt phần trước khi user đăng nhập lần đầu trên app.
Bước 7 — Kiểm chứng bằng một tài khoản test xuyên nền tảng. Tạo một tài khoản test, thao tác lần lượt trên web → iOS → Android, rồi vào User Profile trong công cụ. Bạn phải thấy một profile duy nhất chứa event từ cả ba nguồn (kiểm tra bằng property phân biệt platform). Nếu thấy nhiều profile, identity đang vỡ.
Lỗi thường gặp & mẹo
Lỗi 1 — user_id không nhất quán giữa các nền tảng. Như ví dụ fintech ở trên: web dùng email, app dùng UUID. Mẹo: chốt identity key trong tracking plan và code review mọi lệnh identify để đảm bảo cùng nguồn.
Lỗi 2 — Gọi identify quá muộn hoặc quá sớm. Gọi trước khi backend xác nhận user_id → identify với giá trị null/rỗng. Gọi quá muộn (vài màn hình sau login) → mất event trung gian. Mẹo: đặt identify ngay tại callback login thành công, không sớm hơn không muộn hơn.
Lỗi 3 — Quên reset() khi logout. Trên thiết bị dùng chung (rất phổ biến ở Việt Nam với điện thoại gia đình), hành vi của người dùng mới bị gán vào identity người cũ. Mẹo: luôn gắn reset() vào luồng logout, và test bằng cách logout rồi login tài khoản khác.
Lỗi 4 — Lạm dụng alias trong Mixpanel. Gọi alias nhiều lần cho cùng user gây lỗi merge không thể sửa. Mẹo: nếu dùng ID Merge hiện đại, ưu tiên identify; chỉ dùng alias đúng một lần tại thời điểm đăng ký lần đầu.
Lỗi 5 — Bỏ quên server-side events. Nhiều event quan trọng (thanh toán thành công, gia hạn subscription) phát sinh từ server, không từ client. Nếu server gửi event mà không đính kèm đúng user_id, chúng trôi lơ lửng không gắn vào ai. Mẹo: server-side tracking (học kỹ ở Bài 35) phải luôn gửi kèm cùng user_id đã thống nhất.
Mẹo vàng: thêm một property platform (web / ios / android) vào mọi event. Nó vừa giúp bạn debug identity, vừa cho phép phân tích hành vi theo nền tảng mà không cần chọc vào cấu trúc identity.
Bài tập thực hành
- Vẽ identity lifecycle của sản phẩm bạn đang làm (hoặc một sản phẩm bạn dùng hằng ngày). Liệt kê: điểm nào user còn ẩn danh, điểm nào bạn biết được user_id thật, và bạn sẽ gọi identify ở đâu trên từng nền tảng.
- Kiểm tra identity key. Nếu bạn có quyền truy cập một dự án Mixpanel/Amplitude thật, mở một User Profile và xác định: distinct ID/user ID đang dùng giá trị gì? Có phải cùng nguồn trên mọi nền tảng không? Nếu chưa, viết ra cách khắc phục.
- Thiết kế test case xuyên nền tảng. Viết một kịch bản test 6 bước (duyệt ẩn danh web → đăng ký web → cài app → đăng nhập app → thực hiện hành động → logout) và mô tả kết quả bạn kỳ vọng thấy trong công cụ analytics ở mỗi bước.
- Tình huống xử lý lỗi. Giả sử retention Day-1 của app bạn báo 70% (cao bất thường). Liệt kê 3 giả thuyết liên quan đến identity có thể gây ra con số ảo này, và cách bạn kiểm chứng từng giả thuyết.
Tóm tắt
Cross-platform tracking giải một bài toán tưởng nhỏ nhưng chi phối toàn bộ độ tin cậy của dữ liệu: một con người, nhiều thiết bị, phải là một identity duy nhất. Nếu không, mọi funnel, retention, cohort và LTV đều sai lệch một cách âm thầm và có hệ thống.
Những điều cần khắc cốt:
- Mặc định, mỗi thiết bị = một anonymous ID = một "user" khác nhau. Nhiệm vụ của bạn là hàn chúng lại.
- Chìa khóa là một user_id nhất quán trên mọi nền tảng, chốt trong tracking plan trước khi code.
- Gọi
identify(user_id)đúng thời điểm (ngay sau login thành công), set user properties sau đó, vàreset()khi logout. - Luồng web-to-app-install cần thêm deferred deep link, vì identify một mình không bắc được cầu qua giai đoạn cài đặt.
- Server-side events cũng phải mang đúng user_id.
- Luôn kiểm chứng bằng tài khoản test xuyên nền tảng — thấy một profile duy nhất mới là đúng.