Selling a file at 2am without being awake.
Our own £39 template range promised delivery "within 24 hours" — which meant somebody noticing a receipt, finding the right zip, and attaching it. On a site whose argument is that businesses should not run on somebody remembering, that was the weakest thing on it.
An impulse purchase that waits until morning.
A £39 digital product is bought on impulse. Somebody reads the page late, decides it is worth an afternoon of their time, and pays. The gap between paying and having the thing is where that decision either holds or curdles.
"Within 24 hours" is a promise that the gap could be a whole night. It is also a promise tethered to an inbox: the product cannot be sold to anyone in a timezone where the seller is asleep without the seller being asleep on the job.
The files themselves are the paid product, so they cannot sit anywhere publicly reachable. A guessable URL, a public bucket, or a path inside the deployed site all mean the thing being sold is also being given away to anyone who tries.
So the job was not only automation. It was automation that hands over a private file to exactly the person who paid for it, and to nobody else, without anyone in the loop.
Payment in, files out, nothing stored.
A webhook that verifies before it acts
Stripe tells the site a checkout completed. The signature is verified against the raw request body before anything else happens — parsing the body first and re-serialising it changes the bytes and fails verification for reasons that look like nothing at all. An unverified event fulfils nothing.
Prices mapped to files
The purchased price identifiers are read from the session and mapped to the right files, so a single product and a bundle of three come out of the same code path. The mapping is configuration rather than code, because those identifiers differ between test and live mode and hardcoding one makes the other unusable.
Links that carry their own claim
Each download link states which file, which purchase, and when it stops working, with a signature over all three. Tampering with any field invalidates it. There is no table of tokens to store, nothing to clean up when one expires, and no database to consult when one is used.
Private storage, streamed through the site
The zips are kept out of the repository and out of every public path, in private storage. A valid link streams the file through the site, so the storage location is never published and a link can be made to stop working.
Failure modes chosen so the buyer never loses
A delivery failure returns an error Stripe will retry. A paid checkout whose prices match nothing sends us an alert to fulfil it by hand rather than disappearing into a log. Duplicate suppression is best-effort, so the residual bug is a buyer occasionally receiving their links twice — the right way round for it to be wrong.
An error message pointing at the wrong thing.
The bug worth writing up was not in the fulfilment. It was in what the system said when fulfilment could not run.
Missing config, reported as a bad signature
A missing configuration value was being caught by the signature handler and logged as a verification failure. The endpoint answered as though the request could not be trusted, when the request was fine and the setup was not.
That sends whoever is debugging straight to the signing secret — the one thing that was not wrong — with the payment dashboard showing failed verification to confirm the wrong diagnosis.
Fail as what it is
Configuration is now checked before the signature block, and reports itself as a configuration error. It also returns a status the payment provider retries, so once the value is set the event is delivered again rather than having been discarded.
Same outcome for the buyer either way. The difference is whether the person fixing it spends ten minutes or an afternoon, and whether the paid event survives the wait.
Verified on a real purchase, not a test object.
- +Checkout to zip in the buyer's inbox in seconds, on a live purchase with a live card rather than a simulated one.
- +The delivered file's hash matched the source byte for byte, so the upload, the private read and the stream all preserved it exactly.
- +The download route rejects forged, tampered, expired and wrong-secret links, each checked deliberately rather than assumed.
- +The files are not reachable at any public path. Guessing the filename returns nothing.
The automation is easy. The diagnosis is the work.
Wiring a payment event to an email is an afternoon. Most of what separates that from something you can leave running is in the parts nobody demos: what happens when a value is missing, whether the provider will try again, and whether the error text points at the fault or away from it.
An automation that fails loudly and accurately is worth more than one that almost never fails. The first costs you ten minutes on a bad day. The second costs you a customer who paid, received nothing, and had to tell you about it themselves.
Still fulfilling by hand?
Tell us what you sell, how it reaches the buyer today, and what happens when you are asleep. We reply within one working day. No calls required.