Privacy-First Closed-Loop Attribution Strategy
This document outlines the strategy for measuring exact ROI from both Google Ads and Organic Social Posts, connecting top-of-funnel clicks to bottom-of-funnel Zenchef revenue (POS bills) in a privacy-compliant way.
Background & Challenge
Currently, platforms like Google and Meta report on "clicks" or "landing page views," but they don't know if that user actually booked a table and spent €200. This results in optimizing for vanity metrics. By implementing "Server-Side Offline Conversion Tracking," we can measure real revenue and train the ad algorithms to find high-value guests.
The Tracking Strategy
We will use a First-Party Cookie + Server-Side Match approach. This is highly privacy-compliant because it avoids invasive third-party tracking pixels (like the Meta Pixel) and relies entirely on our own server data.
1. Google Ads Attribution (gclid)
When a user clicks a Google Ad, Google appends a ?gclid=... (Google Click Identifier) to the URL.
- Capture: A middleware script on
michiu.nldetectsgclidand saves it in a first-party cookie (valid for 30 days). - Booking: When the user books via the Zenchef widget, the
gclidcookie is read and passed along to our backend/api/reservation-track. - Match: The backend stores this in a
click_trackingtable. When the Zenchef webhook fires (Reservation Created), we link the reservation to the click. - Offline Upload: Once the guest finishes their meal and the POS bill is closed, a daily cron job uploads the
gclid+ € amount back to Google Ads via their API. This trains Google's Smart Bidding.
2. Organic Social Attribution (m_campaign)
Organic platforms (Instagram, Facebook, LinkedIn) do not automatically append tracking IDs that we can upload back to them for ROI. Instead, we track this internally in the Marketing OS.
- Link Generation (Zuck Helper): When Zuck generates a post for a Campaign, it also generates a tracking link (e.g.,
michiu.nl/promo/spring26which redirects tomichiu.nl/menu?m_campaign=spring26). - Capture: The same middleware on the website detects
m_campaignand saves it in a first-party cookie. - Booking: The
m_campaignID is passed during the Zenchef booking. - Internal ROI: The OS calculates the total POS revenue generated by guests who booked with the
m_campaignID, attributing real € value to the organic social post within the Campaign Dashboard.
Privacy & Compliance (GDPR)
[!IMPORTANT] Cookie Consent Required Since we are using a first-party cookie to track attribution for marketing purposes, the user must accept "Marketing / Analytics Cookies" on the website's cookie banner before we set the cookie.
- No Third-Party Pixels: We do not send raw guest data to Facebook or Google directly from the browser.
- Server-Side Control: We only send data to Google Ads via the API after the visit, giving us full control.
- Hashing: If we fallback to matching users by Email/Phone instead of the cookie ID, the data is hashed using SHA-256 (lowercase, trimmed) before it leaves our server. Google never sees the raw email.
- Consent Flag: Zenchef reservations must include a
marketing_opt_inflag. We only process offline conversions for users who have consented.
Open Questions
[!WARNING] To proceed, please clarify the following:
- Do we have access to edit the
michiu-websitecodebase to add the first-party cookie tracking middleware?- Does the Zenchef booking widget allow us to pass custom metadata (like our cookie ID) through the booking flow so it appears in the webhook payload? (If not, we must rely entirely on email/phone matching).
Proposed Changes
Database Layer
[NEW] src/marketing-os/ads/click-tracking-store.ts
Creates the click_tracking table to store click_id (gclid or m_campaign), type (ad or organic), first_seen, reservation_id, and conversion_uploaded_at.
API & Webhook Layer
[NEW] src/app/api/reservation-track/route.ts
Endpoint for the website to ping when a user starts the booking flow, passing their cookie IDs.
[MODIFY] src/app/api/webhooks/zenchef/route.ts
Updates the webhook handler to match incoming reservations with the click_tracking table.
Upload & Reporting Layer
[NEW] src/marketing-os/ads/conversion-uploader.ts
Daily cron job that queries completed reservations with a gclid and uploads them to the Google Ads API.
[MODIFY] src/marketing-os/campaigns/lifecycle.ts
Calculates organic ROI by querying completed reservations linked to an m_campaign ID and updating the kpi_actuals of the Campaign object.
Verification Plan
Manual Verification
- Click a test URL with
?gclid=test1234and verify the first-party cookie is set in the browser. - Complete a test booking in Zenchef and verify the webhook links the reservation to
test1234in our database. - Manually mark the reservation as "completed" with a €100 bill and verify the offline conversion uploader queues it for upload to Google.