In cash-on-delivery e-commerce the sale is not finished when the customer submits a form. A lead arrives, a salesperson confirms it by phone or chat, and only then is there an order. If the ad platform only sees the form submission, it optimises for people who fill in forms, not for people who buy.
A browser pixel cannot see the confirmation step, and it loses events to ad blockers and closed tabs. So we send conversion events from the server.
The pipeline
- Landing pages and the store send page views, form interactions and leads to a webhook.
- The automation layer validates each event, hashes customer identifiers, attaches campaign information, and forwards it to the ad platforms. It also writes every event to a log sheet for auditing.
- Confirmed orders come from our point-of-sale system. A poller pulls new orders from the POS API and sends them as purchase events with the same customer identifiers as the original lead.
Deduplication
Ad platforms count an event once if two reports share the same event ID. We first generated that ID in the browser, per form submission. When a customer submitted twice, or opened the page on a second device, the IDs differed and the lead was counted twice.
We changed the ID for lead and purchase events to a hash of the customer's phone number. The same person now produces the same ID wherever the event comes from, and the over-count went away.
One tracking engine for every landing page
We run more than a dozen landing pages. Each used to carry its own copy of the tracking script, so one fix meant editing every page. We moved the logic into a single shared script, loaded by every page with a small per-page configuration. A fix now ships to all pages at once.
The first fix it delivered was a race condition: when a visitor submitted the form and the page redirected immediately, the final lead event was sometimes lost. The shared engine now flushes pending events before the redirect.
Polling instead of webhooks for orders
We started with POS webhooks for purchase events and found gaps: some orders never produced a webhook. We replaced them with a poller that asks the POS API for new orders on a schedule and remembers what it has already sent. The webhook endpoints now accept requests only from that poller.
Failures worth remembering
- A deleted header cell. Someone cleared one header cell in the event log sheet. The append step failed silently for three days. Ad platforms still received events, but our own log did not. We now treat the header row as part of the system and never edit it by hand.
- An expired token. A POS access token expired after ninety days and purchase events stopped. We moved to per-shop API keys that do not expire.
More from VIREXAGROUP: Hourly ad and sales reports · A Claude-powered support assistant