The Username That Scared the Shit Out of Me

A hooded figure in a field, face obscured, holding one finger to their lips in a shushing gesture, in black and whitePhoto: Rob Griffin
On this page

At 17:38 today an email landed in my inbox from our own demo blog.

You have a new free member Test PG-254 (4wyfcqek@birdymail.me) Source: Integration: PayGlue

The next ninety seconds were stupid. The ending is the nicest thing that happened to me this month.

First reaction: fine, normal, good even

People sign up on our demo blog all the time. That is what it is for. You go to the demo, you buy a membership through Polar’s sandbox with a test card that charges nobody, and you watch what PayGlue does with it. The paywall opens. The labels appear. Then you close the tab and get on with your day.

So a new member on the demo is not news. A new member on the demo is the product working.

Second reaction: wait

Then I read the name again.

Test PG-254.

PG-254 is a ticket. Our ticket. It lives in our issue tracker, which is self hosted, on our own server, behind our own login. It is not on the website, it is not in the docs, and nobody outside this company has any reason to know that PG-254 is called “One product, one outcome.”

Somebody had just signed up to our demo using the internal name of one of our own tickets.

The ninety seconds

The brain does not go to the boring explanation first. Mine went:

Is this a joke? Does somebody I know think this is funny?

Is it a coincidence? Could a stranger type “Test PG-254” into a name field by accident? No. That is not a thing that happens.

Did somebody get into the tracker? Is this a very polite person telling me they got into the tracker?

Did I do this and forget? Increasingly plausible with every passing month, but no, I had not touched anything.

I want to be honest about the physical part too, because founders never write this bit down. My hands got cold. Not dramatic, not a crisis, just that specific unpleasant temperature drop you get when a system you built does something you did not ask it to do, using information it should not have.

It gets better. I was at family dinner. The sacred one, where the phone stays face down.

I excused myself and stood up, and my wife read my face before I said anything. She asked whether someone we love had been taken to hospital. Thankfully not, and I told her so, and I also told her there was something urgent I had to go and look at right now.

That is the honest comparison. If your house is on fire you call the fire brigade from the table. You do not finish the roast and mention it over coffee. You do not yet know whether it is a candle or the whole kitchen, and the not knowing is exactly why you get up.

The boring, wonderful explanation

On the eleventh of August, two weeks ago, I tested PG-254. I made a test purchase on the demo through the Polar sandbox, named the customer after the ticket I was testing, the way you do, confirmed the thing worked, then deleted the member from Ghost because I am tidy, and moved on.

What I did not think about was the product I had bought. It has a fourteen day trial.

And no, I had not remembered that. Of course I had not. I could not tell you what I ate fourteen days ago either. Could you?

This is the part people get wrong about sandboxes. Polar’s sandbox is not a toy that pretends to be a payment provider. It runs the same machinery as the live product: the same subscription logic, the same trials, the same renewal clock, the same webhooks. The only thing it leaves out is moving real money, which is the entire point. If the sandbox behaved differently from production, testing in it would tell you nothing.

Which also means a sandbox subscription you never cancel behaves like any other subscription. It keeps running.

trial_start:  2026-08-11 15:38:53
trial_end:    2026-08-25 15:38:49

Fourteen days, to the second. On the eleventh I started a clock and walked away from it.

Today it ran out. Polar did what a payment provider is supposed to do when a trial ends: it converted the subscription, billed the renewal, and sent us a webhook. Our backend did what it is supposed to do with that webhook, which is look up the member by email and act on what it finds.

That last part is the whole story. Our code has two paths:

member_id, existing_note = self._find_member(members_url, email, headers)

If it finds the member, it updates them. New labels, note appended, nothing in my inbox.

If it does not find them, it creates them, and creating a member in Ghost counts as a signup, and a signup notifies the publisher.

I had deleted that member two weeks earlier. So there was nothing to find, so it created one, so Ghost emailed me.

Here is the member it built, twenty five minutes after the fact:

The Ghost member record for Test PG-254. Four labels are set: source:payglue, product:product-e910af, payglue-active and payglue-provider:polar. The internal note reads “Direct via PayGlue, Provider: polar”, followed by the product ID, the order date 2026-08-25 and the event ID. Signup source is listed as Integration: PayGlue.

Look at what it filled in on its own. Four labels, so the paywall knows this person is active and where they came from. A note carrying the product, the order date and the event ID, so that in six months I can trace this member back to the exact webhook that made them. Nobody typed any of that.

Every step in that chain is correct. The scary part was not a bug. The scary part was me, being tidy, two weeks ago.

What this actually proved

Once the temperature came back into my hands, I realised what I had been handed.

Renewals are the part of a payment integration you cannot really test on demand. A purchase you can test in ten seconds. A cancellation in twenty. But a renewal happens when the provider decides it happens, weeks later, with nobody watching, and if it fails quietly you find out from an angry customer whose paywall shut in their face for no reason they can see.

I never scheduled a renewal test. I accidentally scheduled one on the eleventh of August and then forgot, which is arguably the better version, because I could not have nudged the result even if I had wanted to.

Fourteen days later, unattended, on a Tuesday evening, while I was doing something else entirely:

  • Polar ended the trial on time and billed the renewal
  • The subscription_cycle webhook arrived
  • Our backend processed it, found no member, created one
  • Labels were set, the note was written, the access was granted
  • Ghost told the publisher, exactly as it should

Nobody touched anything and it worked.

The bit I got wrong on the way

For a few minutes I thought I had found a real bug, and I am leaving it in, because the correction is more interesting than the scare.

The email said “free member”. The order was paid. That looked wrong to me.

It is not wrong. If a Ghost site has no Stripe connection, and our demo does not, then Ghost has no paid membership to hand out in the first place. PayGlue does not pretend otherwise. It creates a free member and grants the access through labels instead, which is the entire premise of the product: the entitlement belongs to us, not to Stripe.

comped = stripe_connected and is_grant

That one line decides whether we tell Ghost the truth about what a member actually has.

I had to go and read our own code to be sure of it. Which is its own small lesson. I built this, and I still checked, because being fairly sure is not the same as knowing.

So

Our webhooks work well enough to frighten the person who wrote them, at dinner, on a Tuesday.

If you are running PayGlue and a signup notification turns up that you did not expect, it is worth checking whether you left a trial running somewhere two weeks ago. And if you deleted the test member afterwards, that is why you got an email instead of silence.

Now if you’ll excuse me, I have a test user to delete. And this time, a subscription to cancel as well. Ask me in fourteen days whether I remembered the second part.

Photo by Rob Griffin on Unsplash