thegioikeycap.vn — Tracking Meta Pixel + Conversions API
Plugin tgk-tracking tại wp-content/plugins/tgk-tracking/.
Pixel: 1284965829812105
Là plugin riêng, không nằm trong theme — tracking phải sống độc lập với giao diện, đổi/nâng cấp theme không được làm mất dữ liệu chuyển đổi.
1. Event đang bắn
| Event Meta | Kích hoạt | event_id | Giá trị |
|---|---|---|---|
PageView |
mọi trang frontend | pv.{random} |
— |
ViewContent |
chỉ trang chi tiết sản phẩm | vc.{random} |
giá sản phẩm |
AddToCart |
woocommerce_add_to_cart (cả AJAX và form) |
atc.{random} |
giá × số lượng |
InitiateCheckout |
load trang /thanh-toan/ |
ic.{hash phiên+giỏ} |
tổng giỏ |
CompleteRegistration |
woocommerce_created_customer, user_register |
reg.{user_id} |
— |
Purchase |
trang cảm ơn + đơn sang processing/completed |
pur.{order_id} |
tổng đơn |
PageView khác ViewContent thế nào
PageView bắn ở mọi trang và không mang thông tin gì — Meta chỉ biết "có người vào site".
ViewContent chỉ bắn ở trang sản phẩm và mang content_ids + giá — Meta biết "người này xem sản phẩm X giá Y".
Cần ViewContent vì: (1) retarget được "ai xem sản phẩm X mà chưa mua"; (2) Catalog/Dynamic Ads bắt buộc phải có nó; (3) Meta học chuỗi ViewContent → AddToCart → Purchase để tìm người giống khách đã mua — thiếu mắt xích đầu thì tín hiệu yếu hẳn.
2. Dedup — thiết kế cốt lõi
Meta gộp 2 event thành 1 khi trùng cả event_name và event_id, trong cửa sổ ~48 giờ.
Ba quyết định làm nên việc đó:
a) event_id do SERVER sinh, rồi truyền xuống browser.
Nếu để mỗi bên tự sinh thì hai ID không bao giờ trùng và Meta đếm đôi mọi chuyển đổi. Base pixel ở đây cố tình không gọi fbq('track','PageView') như snippet mặc định của Meta — snippet đó không kèm eventID nên không dedup được.
b) Event có định danh nghiệp vụ dùng ID tất định.
Purchase → pur.{order_id}. Khách F5 trang cảm ơn 10 lần, webhook SePay về, đơn đổi trạng thái — tất cả đều sinh ra đúng một event_id.
c) UNIQUE KEY trên event_id ở tầng database.
INSERT IGNORE nên lần ghi thứ hai bị chặn im lặng. Đây là chốt cuối, không phụ thuộc vào logic PHP có đúng hay không.
Đã kiểm chứng: gọi on_purchase(1707) 6 lần → 1 event, tổng doanh thu ghi nhận đúng 450.000đ (không đội).
3. Vì sao Purchase móc vào 3 chỗ
- COD: khách thấy trang cảm ơn → bắn tại đó.
- SePay QR: khách có thể tắt trình duyệt trước khi chuyển khoản xong; đơn chỉ được xác nhận khi webhook về. Không móc vào
woocommerce_order_status_processinglà mất hẳn chuyển đổi đó.
Bắn nhiều chỗ mà không đội số là nhờ (b) + (c) ở trên.
Bẫy đã xử lý: IP/UA khi bắn từ webhook
Webhook SePay là request của server SePay, không phải của khách. Lấy $_SERVER lúc đó sẽ gửi IP/UA của SePay lên Meta → match sai người.
Xử lý: woocommerce_checkout_create_order chụp _fbp/_fbc/UTM vào meta đơn ngay lúc khách đặt; khi bắn Purchase thì đọc IP/UA từ $order->get_customer_ip_address() / get_customer_user_agent() và fbp/fbc từ meta đơn.
4. Luồng dữ liệu
Hành vi khách
│
├─→ TGK_Events::record()
│ ├─ ghi bảng wp_tgk_events (NGUỒN SỰ THẬT, lưu cả PII thô + UTM)
│ ├─ xếp hàng CAPI ──→ cron mỗi phút ──→ Graph API /events
│ └─ xếp hàng client ─→ fbq('track', name, params, {eventID})
│
└─→ Meta nhận 2 bản cùng event_id → tính 1 chuyển đổi
Vì sao không gửi CAPI đồng bộ trong request của khách: mỗi lần gọi Graph API mất 100–800ms, cộng thẳng vào TTFB — đúng thứ vừa tối ưu được khi bỏ Astro. Nên: ghi DB (nhanh, cục bộ) → cron gửi theo lô.
Ngoại lệ: Purchase được thử gửi ngay (send_now) vì quan trọng nhất và số lượng thấp; lỗi thì cron vẫn thử lại tới 5 lần.
Thêm giỏ bằng AJAX không tải lại trang, nên event client được nhồi vào fragment div.tgk-events của WooCommerce; assets/js/tracking.js đọc ra và bắn ngay khi WooCommerce phát sự kiện added_to_cart.
5. Dữ liệu gửi Meta (match level full)
Mọi PII đều SHA-256 sau khi chuẩn hoá. Chuẩn hoá sai thì hash khác nhau và Meta không match được — đây là nguyên nhân match rate thấp phổ biến nhất.
| Field | Nguồn | Chuẩn hoá |
|---|---|---|
em |
email đơn/user | trim + lowercase |
ph |
điện thoại đơn | chỉ số, kèm mã quốc gia: 0979 481 951 → 84979481951 |
fn ln |
tên đơn/user | lowercase, bỏ ký tự không phải chữ/số |
ct st zp country |
địa chỉ đơn | lowercase, bỏ khoảng trắng/dấu câu |
external_id |
user ID | hash |
fbp |
cookie _fbp |
không hash |
fbc |
cookie _fbc, hoặc dựng từ fbclid dạng fb.1.{ts_ms}.{fbclid} |
không hash |
client_ip_address |
IP thật (bỏ IP nội bộ) | không hash |
client_user_agent |
UA | không hash |
Dữ liệu thô (email/phone chưa hash, UTM, referer) chỉ lưu ở cột raw_request trên server này, không gửi ra ngoài. Đó là ý "lưu toàn bộ dữ liệu về server rồi mới gửi payload enrich".
Đổi mức: option tgk_tracking_settings['match_level'] = full | contact | minimal.
5b. LocalStorage Attribution & Multi-Touch Tracking (WMT Kit)
Bổ sung từ phiên bản 1.1.0 (2026-09-20) theo docs/TRACKING_LOCALSTORAGE_ATTRIBUTION_SPEC.md.
Vấn đề trước đây
Trước đây plugin chỉ đọc $_GET lúc request xử lý. Khi khách bấm link quảng cáo vào landing page rồi chuyển trang xem sản phẩm, vào giỏ, sang thanh toán (/?wc-ajax=checkout), $_GET hoàn toàn rỗng $\rightarrow$ mất attribution (>80% đơn bị gán "Direct") và không có fbc cho CAPI nếu cookie _fbc bị Safari ITP chặn.
Cơ chế hoạt động
- Client-side (
assets/js/tracking.js): -tgk_anon_id: UUID v4 định danh thiết bị ẩn danh, sinh 1 lần và lưu vĩnh viễn tronglocalStorage. -tgk_last_touch: Lưu JSON gồm toàn bộ UTM và Click ID (utm_source,utm_medium,utm_campaign,utm_content,utm_term,utm_id,fbclid,gclid,gbraid,wbraid,gad_source,gad_campaignid,ttclid) kèm timestamptsvà TTL 30 ngày. Cập nhật khi có tham số mới. -tgk_first_landing: Lưu URL đầu tiên khách đặt chân vào site (kèm query string). -tgk_first_referrer: Lưudocument.referrerban đầu. - Checkout Bridge: Tự động inject hidden inputinput[name="tgk_attribution"]vàoform.checkoutkhi tải trang, khi WooCommerce bắnupdated_checkout/init_checkout, và trước khi submit đơn. - Server-side (
includes/class-events.php): -capture_order_attribution( $order )đọctgk_attributiontừ$_POST(fallback vềself::utm_params()nếu tắt JS). - Tự động dựng_tgk_fbctừfbclidtrong LocalStorage nếu cookie_fbcbị Safari chặn:fb.1.{time_ms}.{fbclid}. - Lưu vào Order Meta:_tgk_utm,_tgk_fbc,_tgk_fbp,_tgk_anon_id,_tgk_first_landing,_tgk_first_referrer. - Quản trị đơn hàng (
includes/class-admin.php): - Meta box "Nguồn đơn hàng" trong màn hình đơn hàng WooCommerce hiển thị trọn vẹn: Nguồn, Kênh, Chiến dịch, Quảng cáo, Click ID (Facebook/Google/TikTok), Thiết bị (UUID), Trang đầu, và Tín hiệu khớp Meta (fbp·fbc).
5c. Tối ưu tải máy chủ & Giám sát RUM (2026-09-24)
- Loại bỏ pingback tự DDoS: Trước đây client bắn
fetch(/wp-json/tgk/v1/event)trên mọi PageView và ViewContent chỉ để đánh dấu cộtclient_fired = 1trong wp-admin, làm nhân đôi số lượng request vào PHP-FPM worker. Đã gỡ bỏ pingback trên PageView/ViewContent, chỉ giữ lại trênPurchase. - Hàng đợi client stub queue (
window.tgkTrack): Hứng các lệnh gọi tracking trước khi script tải xong, tương thích tải bất đồng bộ qua GTM mà không mất sự kiện. - RUM Core Web Vitals: Tích hợp
PerformanceObservertự động đo TTFB, FCP, LCP, CLS thực tế của khách vàowindow.TGK_Vitals(chuẩn nhưapex-tracker.js).
6. Bảo mật
| Rủi ro | Xử lý |
|---|---|
Client tự đặt event_id = pur.{order_id} của đơn thật → Meta coi Purchase thật là trùng và bỏ qua |
Endpoint REST chỉ nhận event_id khớp ^(atc\|pv\|ic\|vc)\.[a-z0-9]{8,32}$; từ chối pur./reg. |
| Client bơm giá trị chuyển đổi | Giá luôn tính lại từ DB theo product_id, không lấy từ client |
| Spam endpoint | Nonce wp_rest + lọc bot |
| Bot làm loãng dữ liệu | Lọc UA (bot, crawl, ahrefs, facebookexternalhit…) + loại admin/cron/REST/feed. Không lọc thì mỗi lần Googlebot quét là một PageView gửi lên Meta |
| Token lọt vào backup DB | Token ở hằng số TGK_CAPI_TOKEN trong wp-config.php, không lưu DB |
7. Quản trị
wp-admin → TGK Tracking: trạng thái cấu hình, thống kê 7 ngày, 50 event gần nhất kèm phản hồi của Meta, nút "Gửi hàng đợi ngay" và "Thử lại event lỗi".
Cột "Có bắn client" thấp hơn "Số lượng" là bình thường — đó chính là phần khách dùng chặn quảng cáo mà chỉ server-side thu được. Đây là thước đo giá trị của việc chạy song song 2 đường.
Cron: tgk_tracking_dispatch mỗi phút, tgk_tracking_prune mỗi ngày (giữ log 180 ngày).
8. Kết quả kiểm chứng
Đã chạy đơn hàng test end-to-end thật (2026-09-04, qua Test Events code TEST8224, không vào dữ liệu thật; đã tắt tạm send-app để không bắn thông báo cho khách/chủ shop).
Toàn bộ funnel đi qua form checkout thật:
| Event | Số lượng | Giá trị |
|---|---|---|
| PageView | 30 | — |
| ViewContent | 1 | 450.000đ |
| AddToCart | 2 | 900.000đ |
| InitiateCheckout | 2 | 900.000đ |
| Purchase | 1 | 450.000đ |
- Meta trả
events_receivedkhớp số gửi,messages: []— không warning nào - Dedup trên đơn thật: 1 lần đặt + 4 lần F5 trang cảm ơn → đúng 1 Purchase. Các ID auto-increment bị nhảy (46, 48, 50) chính là 3 lần trùng bị
UNIQUEchặn ở tầng DB. - Hash điện thoại đúng:
0979481951→ chuẩn hoá84979481951→ hash khớp chính xác giá trị gửi lên Meta user_datađủ 8 field hash (em, ph, fn, ln, ct, st, zp, country) + IP + UA thật- Đơn
cancelled→ không ghi event fbcdựng lại từfbclidđúng format Meta- TTFB không đổi: ~0,12s (trước tracking ~0,12–0,18s)
Sau khi test: đơn #1717 đã huỷ để hoàn kho (2 → 1 → 2) rồi xoá hẳn; send-app bật lại; test code tắt; log event dọn sạch.
Khi xoá đơn test, phải huỷ trước rồi mới xoá. Xoá thẳng thì WooCommerce không hoàn kho → tồn kho lệch âm thầm.
Bug đã tìm và sửa trong lúc dựng
should_track()chặn AJAX → mất hẳnAddToCarttừ nút icon trên lưới (đường thêm giỏ chính).- Purchase từ webhook gửi IP/UA của server SePay thay vì của khách.
wp_schedule_event()gọi trước khi khai báo chu kỳtgk_minute→ trảfalsetrong im lặng, cron không bao giờ chạy, event nằm mãi ởpendingmà không có lỗi nào hiện ra.
9. Kiểm thử lại sau này
Muốn test mà không đụng dữ liệu thật:
- Lấy code ở Events Manager → Test Events
- Bỏ comment dòng trong
wp-config.php:define( 'TGK_TEST_EVENT_CODE', 'TESTxxxx' ); - Tắt tạm
send-app(hookwoocommerce_new_order→ bắn thông báo thật cho khách/chủ shop) - Test xong: comment lại test code (để nguyên thì mọi chuyển đổi thật biến mất khỏi báo cáo quảng cáo), bật lại
send-app, huỷ-rồi-xoá đơn test
10. Việc còn lại
- Catalog / content_ids — hiện dùng product ID của WooCommerce. Nếu chạy Advantage+ Catalog Ads thì ID trong feed sản phẩm phải khớp giá trị này, không thì mất khớp sản phẩm và Dynamic Ads không hoạt động.