Essays / Six Products, Two Sales.

Essay · 11 August 2026 · Adrian Verdan

Six Products, Two Sales, One Customer

This report first appeared on Medium. The full original text is below. The same version on Medium →

Six products, five weeks, and a single buyer who bought twice. I audited my own website counters to find out why, found one of them wrong, found the second only because somebody else read the draft, and lost a claim I had been carrying since July.

Someone bought a 12.90-euro guide from me on July 6. On July 31 the same person came back and bought a second book, 39.00 euros on the label, 35.10 euros paid. Gumroad records the same buyer identifier on both rows, which is how I know this is one customer and not two. That is the complete customer history.

Drafted with AI assistance; however, every number, failure, and opinion in here is mine

The shop is five weeks old. The first product went live on July 5, the sixth on July 17, and the first sale arrived one day after there was anything to sell. Five weeks is not long enough for two sales to mean much in either direction, and I am about to spend the rest of this treating them as a problem anyway. The two payments come to 48.00 euros before Gumroad's cut.

Two Sales, One Buyer, Twenty-Five Days Apart

The shelf is three guides, each in German and English. July 6: the German guide to registering a one-person business, list price, no code. July 31: the English operating playbook, and the discount field on that row holds the string WEBSITE10, a code I hand out in exactly one place. Neither purchase was refunded.

Somebody is going to read a repeat purchase as retention, and I would like to as well. One person buying twice, twenty-five days apart, is not a retention rate. The only thing it establishes is that I have no retargeting, no remarketing and no mailing list the second purchase could have come from. Whether that person is a stranger to me is the fair question at n=1, and the answer is that they are one: somebody I have never met, who arrived from somewhere I cannot name.

Gumroad charges a discovery fee when its own marketplace produces the sale. Neither row carries one, so I wrote in my notes that the platform I sell on has never introduced me to anybody, and that one of the two sales came through the only channel I own.

A Week of Watching My Own Shelf

Neither of those notes survived the week, but I did not know that yet, and the question I took to the data was the simpler one: does anybody arrive at all. I count website traffic on the server, without cookies, without storing anything on anyone's device. In the seven days ending August 11 the collector reported 1,187 requests to paths it does not classify as an asset. Two more piles sit next to that number, not inside it: 852 for stylesheets, images, fonts and robots.txt, and 202 from crawlers Cloudflare can positively name on page paths. Together the three are 2,241 requests that returned a 200 to what Cloudflare calls an eyeball. Redirects and errors are in none of the piles.

Bar chart of one week of website requests, split into three separate piles that together make 2,241: 1,187 page requests, 852 asset requests, 202 named crawlers. The 1,187 is broken down further into 513 home page, 145 another project, 51 index pages, 78 product detail pages, and 400 remaining.

Tap to enlarge.

One week of my own site, counted on the server without cookies. Every number is a request that returned a 200, and a request is not a person. The 1,187 needs a correction that arrives at the end of this piece. Data: my Cloudflare zone, 4–11 August 2026

Of the 1,187, the home page took 513. The second busiest page, at 145, belongs to a different project of mine entirely: same site, same domain, and the shop is one room in it. The two product index pages took 51 between them, and each of them lists three products, its own language only. Product detail pages took 78. The remaining 400 are the imprint, the privacy page, the contact form, the rest of the furniture — and 28 requests I come back to at the end.

Now the number I should have chased and did not. On July 25 I ran a spot check on the single busiest page of the busiest day, and 249 of 264 requests on it came from one repeated desktop Chrome user agent with an empty verified-bot field. That is 94 percent, and it is almost certainly me, or a monitor I run. One page, one day, in July: it says nothing directly about the week I am reporting. My collector never stores a user agent, on purpose, so it cannot tell me — but the spot check did not come from the collector. It came from a one-off query, and I had already run that query once, in July. I did not run it again on this week's product pages, and from August 12 the source forgets the fourth.

The Sentence I Almost Published

The product-page counter came back with four rows. I have six products. The sentence wrote itself and sat in my notes for most of a morning: two of my six products were not looked at a single time in a week.

Then I read how the counter builds its output. It loops over the slugs it counted and writes one row for each. A product with no requests produces no row, and so does a product the counter never had a chance to count. The four rows never said two products got zero. They said the counter knew about four things.

Absence of a row is not a zero.

The second control was cheap and reassuring. Could the matcher even see the missing two? It reads the request path with a regular expression rather than checking against a list of products I maintain, so it cannot forget one. That moved the question off the counter and onto the website.

My Second Check Was Wrong in a Nicer Way

I listed the product directories the site publishes. Six of them, carrying four distinct names between them, and those four were exactly the four names with requests. A clean match, and a comfortable one: a hole I had dug myself instead of a verdict on my readers.

Then I opened the six pages and looked at where the buy buttons point.

The page at /produkte/ai-first-operating-playbook/ sells the product whose Gumroad permalink is ki-first-betriebs-playbook. The page at /en/products/kleingewerbe-gruenden/ sells kleingewerbe-in-germany. For two of my three guides the German and the English page end in the same word, while Gumroad gives each language edition its own permalink. Six pages, four names, six products, all of them reachable and all of them in the right language.

And the counter buckets by URL slug, not by URL. Its regular expression captures the last segment of the path and throws the language prefix away, so /produkte/x/ and /en/products/x/ arrive at the same row. That row, labelled ai-first-operating-playbook, holds 29 requests, and it is two products stacked: 16 on the German page and 13 on the English one. The row labelled kleingewerbe-gruenden, 25, is 15 plus 10.

The definition of that metric, in my own catalogue, states that the identifier "is the SAME string used as product_id in gumroad_sales_count", the Gumroad permalink. On August 3 I published a piece with the line in it: a counter that counts two things is two counters. This one had been counting two things since July.

The same claim sits in the adapter that produces the number, and there it comes with an argument. The docstring explains why no language tag is needed in the slug, and it makes the case with an example: kleingewerbe-gruenden for German against kleingewerbe-in-germany for English, distinct permalinks, therefore no collision, "the literal path already guarantees that". That is the exact pair the rule breaks on. The literal path is /en/products/kleingewerbe-gruenden/. I did check. I checked the Gumroad side and wrote down what I saw there as though it settled the website side.

What Thirteen Requests a Week Cannot Tell Me

With the mapping repaired by hand, the shelf for that week reads 16, 15, 13, 13, 11, 10. I have not fixed the counter itself; it still buckets by slug as I write this, which is why the caption below is in the present tense.

Bar chart of one week of product-detail requests for six products, ranging from 16 down to 10, with brackets marking the two pairs that the traffic counter merges into a single row each.

Tap to enlarge.

Product-detail requests for all six products in one week, after mapping every page to the product it actually sells. My counter reports this as four rows, not six. Data: my Cloudflare zone and my website source, 4–11 August 2026

Six requests separate the best from the worst, over a denominator that may be mostly me. One man clicking through his own shelf produces exactly this shape: six bars of roughly equal height. The chart fits an audience and it fits me.

The obvious objection is that the products are simply bad. I cannot rule it out and neither can this data. Nor am I going to divide two by 78: the sales are lifetime, the requests are one week, no sale happened inside that window, and the denominator is not people.

I have written that kind of arithmetic anyway, more than once, at volumes where it could not have meant what I said it meant, and I think I know why I keep doing it. A chart is much easier to make than a reader. I made two charts for this article. I have one customer.

Then Somebody Read the Draft

I handed the draft to a reader whose only job was to disagree with it, and I owe two corrections to that hour. The first: 28 of the 1,187 are requests to a certificate challenge and a Cloudflare diagnostic endpoint. My exclusion rule looks for a file extension, and an ACME token has no dot in it, so it passed as a page I might have written. So the page count is 1,159, not 1,187. The 28 do not vanish when I subtract them: they were always requests, they were only never pages, and they belong in the pile with the stylesheets, which becomes 880. The week is still 2,241. The figure still says 1,187 and 852, because that is what the collector still says.

The second is the sentence about the discovery fee. Gumroad's sales record has a referrer field, five keys along from the fee field, and I had never opened it. The July 6 row says https://gumroad.com/. So one field says the marketplace was not paid for that sale and another says the buyer arrived from the marketplace's own domain, and I built a sentence out of the one I read first. I do not know where that sale came from. Before I opened the field I thought I did.

The July 31 row says direct, and I was two sentences into the same paragraph about it before I stopped. direct is what that field holds when no referrer arrives at all: a copied link, an app, a browser that strips it. It records a missing value, not a different origin. And the coupon code sits in the same row, and that code exists in exactly one place. One half of the note is gone. The other half was never in that field.

And the number I promised. On July 28 I wrote myself a recommendation, a monthly test of the outside models I use to audit my code, and on August 5 I promised to report here whether it existed, whatever the answer was. Today is August 11: fourteen days, and there is no monthly anything. I did build the test itself that morning — one file, three planted defects, one decoy, the answer key committed before the first run. All four runner paths found all three defects, and none of them claimed the decoy. Four paths are three models, because I called one of them two different ways. A result with no variance in it separates nothing, so what I have is a test that tells me the models answer, not how well.

I am not building a seventh product. Six exist, every one reachable, every one drawing between ten and sixteen requests a week from I do not know whom. I cannot rule out that the products are bad; I am choosing to act as though this is a distribution problem, on the grounds that I have never once been able to name how a stranger would find out these things exist. That is a decision under uncertainty and not a finding.

The cheapest thing I sell is that 12.90-euro guide to registering a one-person business in Germany. The German edition is the full-price purchase at the top of this piece. The English edition has never sold to anybody.

I spent a day on this, corrected two counters by hand without fixing either of them, broke a claim I had been carrying since July, and I know exactly as much about the people on the other side of the shop as I did that morning.

Read on

All reports at a glance.

Each report is built on a specific failure, with the date, the log, and the change that followed: to the essay index →