BlogAnalytics

Seven bugs we hit adding attribution

Zero visits on a day with traffic. Zero revenue after two orders. A funnel step that never fired because of one trailing dot. None of these were hard once found, and every one of them looked like something else first.

Context

These came up while a study app integrated Vevee's attribution features (SDK 1.14.0) and we fixed the Vevee side as they surfaced. Everything was tested in test mode with an sk_test_ key, against seeded fake traffic: 90 visits from eight sources with real user agents, nine cities, signups, activations and orders in two currencies. The seeding script refuses to run with any key that does not start with sk_test_, which is the first thing we would recommend copying. All counts below are from that mock data.

1. Traffic showed zero visits for a day that had traffic

The seed ran in the late evening, Italian time. The Traffic page for that day was empty, and the day before had numbers nobody had sent. Vevee groups visits by day in the app's time zone, and the app had never set one, so it was UTC. Evening traffic in Rome landed on the previous UTC day once the clock crossed over. Nothing was lost; it was filed under the wrong date. The fix was one setting: Settings, Time zone, Europe/Rome. The same setting decides which day a first touch records, so it is worth setting before any real traffic arrives.

2. Revenue showed $0 after two paid orders

The orders were stored. The revenue card still said zero. The dashboard reports in USD and converts each order with the ECB reference rate for its day, and the rates table had nothing for those days yet, because the daily rate job had not run since the deploy. Orders in a currency with no rate are kept out of the totals and counted in a "not converted" note under them, which in hindsight was the clue. Fix: the rates load from an endpoint an external worker calls once a day, and a one-time call with backfill=90 loads the last 90 days so older orders convert too.

3. "Activated" stayed at zero

The person funnel had three steps: signed up, activated, paid. Step two never moved, although the app was clearly sending its activation event. The funnel definition had been typed by hand into the JSON file:

"steps": ["signed_up", "generate_succeeded.", "checkout_completed"]

The dot

generate_succeeded with a trailing full stop is a perfectly valid event name that nothing ever sends. Removing the dot fixed it. The lesson we took: an event name that has zero entries over a period where its neighbours have hundreds is almost always a typo, and the builder should say so out loud.

4. A step-level breakdown vanished on import

The app's JSON put a breakdown property on one step. The import succeeded, and the breakdown was simply not there. Our importer only read breakdown at the top level of a funnel and ignored it on steps without complaint. Two fixes on our side: breakdown is now accepted on individual steps (stored as that step's breakdown property), and unknown keys in the shorthand format are now errors with the path in the message, so a field the importer does not understand can never disappear quietly again.

5. One long description failed the whole import

The file held several funnels. One of them had a paragraph-long description, and the importer rejected the entire array because of a 500 character limit nobody had a reason for. We removed the limit; descriptions of any length are accepted now. Arrays still import all or nothing at the parsing stage, and the error names the entry that failed, which is the behaviour you want when a file is the source of truth.

6. The visit split emptied itself after two days

This one was ours and it was the worst of the seven. A visit funnel split by channel looked right on the day it was built. Two days later every row was empty. The first draft of the split read each visit's channel from a working table of visits, and that table is swept after 48 hours as part of the privacy design. Once the rows were swept, the funnel had nothing to split by. The split now reads the channel from the earliest anonymous event of each visit that carries visit fields, which is the same rule the Traffic page uses. That made it stable, and as a bonus the visit funnel now matches Traffic for the same days, row by row, which is how we checked the fix.

7. Signups failed on preview deploys

Production worked. Every preview deploy failed at the signup step with an authentication error from Vevee. The secret key existed in the hosting provider's Production environment and not in Preview, so the auth route called Vevee with an undefined key. The app added a test key to the Preview environment. The alternative is to point previews at production with an sk_test_ key, which keeps preview data out of live numbers either way.

One that looked like a bug and was not

During the privacy review someone found a localStorage key on the dashboard page and asked whether the cookieless claim was false. It was the app's own counter for an onboarding tour: it never leaves the device and has nothing to do with analytics. Worth knowing before your own audit finds it, and worth a sentence in your notes so the next person does not lose an afternoon to it. The full integration, with the parts that would have prevented bugs 1, 2 and 7, is in the traffic attribution guide at /docs/guides/traffic-attribution.

See your own numbers in two minutes.Add the SDK and your users, costs and revenue fill in on the next request.
Join waitlist

Keep reading

ShareCopy a link to this post.

Set your first limit in about five minutes.

50k operations free every month. No card required.
Join waitlist