How a reward gets issued
The reward is the whole point, and the one place a mistake costs real money — a double payout, or a reward for a purchase that never happened. This post is about issuing it safely: how a conversion is confirmed, how both sides are credited exactly once even if the webhook fires twice, and how the thank-you gets written without letting a model near the ledger.
Key takeaways
- The reward is triggered by a conversion webhook — a genuine first purchase — not by a click or a sign-up.
- The pending referral is flipped to rewarded with a conditional write, so a webhook that fires twice can only pay once.
- Both sides are credited in the same step: the referrer’s reward and the friend’s, each written to the ledger exactly once.
- The actual crediting is done deterministically through the store’s API; one Bedrock call only writes the two thank-you notes.
- Anything the fraud screen holds, or a conversion for an already-rewarded referral, is never paid — it stops or goes to a person.
The one step that spends money
Everything until now has been careful bookkeeping: a code claimed, a click logged, a pending referral opened. None of it cost anything. This step is different, because this is where real value leaves the business — £10 of credit to the referrer, £10 off the friend’s box — and it’s the one place a mistake is expensive in both directions. Pay twice and you’ve handed out money you can’t claw back; pay for a purchase that never really happened and you’ve rewarded a phantom. So the reward step is built around a single obsession: credit both sides exactly once, and only for a conversion that’s genuinely real.
The trigger is the conversion webhook. When the referred friend makes their first real purchase — Mara’s first FreshBox actually ships and is paid for, the barber’s new customer pays for their first cut, the window cleaner’s new sign-up settles their first clean — the store fires a webhook to the same Function URL that handles everything else. “First real purchase” is the store’s call, not the model’s: a paid, non-refunded order, past any cooling-off the business sets, so a sign-up that buys and immediately cancels never trips it.
Exactly once, both sides
Webhooks are famously unreliable in one specific way: they arrive more than once. A network hiccup, a retry the store thinks failed, a checkout that double-fires — any of these can deliver the same conversion two or three times. If the reward step simply paid out each time it was called, a flaky network would become free money. So the very first thing it does is claim the payout with a conditional write: flip this referral from pending to rewarded, but only if it’s currently pending. The first webhook wins the flip and proceeds to pay; any later copy finds the referral already rewarded, the conditional write fails, and it stops without paying a second time. Exactly-once isn’t a hope here, it’s a property of that one atomic write.
Only after winning the flip does the step credit the two sides, and it treats them as one unit of work. The referrer’s reward and the friend’s reward are each written to a rewards ledger keyed so that a given referral can only ever record one credit per side. The ledger is the source of truth for “has this been paid?” — separate from the store’s own view — so even if fulfilment through the store has to be retried, the ledger keeps the count honest. Both sides, together, once.
The model writes the thanks, not the cheque
Once both sides are credited, the last thing the step does is say thank you, and this is the only place a model runs at conversion time. A single Bedrock Haiku 4.5 call is handed the facts it may use — the two first names, the business voice, and the reward each side actually received — and writes two short notes: one to the referrer (“Mara just made her first order — your £10 is on your next box, thanks for the recommendation”) and one to the friend (“welcome to FreshBox, your £10 has been applied”). Each goes out by whatever channel suits — SMS through SNS where a mobile is the contact, email through SES otherwise. The reward amounts in those notes come from the ledger the code already wrote, not from the model; Bedrock is told them so the wording is accurate, but it doesn’t get to decide them.
That line matters more than it looks. The model never touches the ledger, never calls the store’s API, never sees a total it could round up. It writes words about a payout that has already, deterministically, happened. If the Bedrock call is slow or the draft is off, a fixed thank-you template goes out instead — the credit is already applied, so a plain note is no loss. The warmth is nice to have; the accuracy is non-negotiable, and keeping them on opposite sides of that fence is what makes it safe to let a model near a system that moves money at all.
The payouts that don’t happen
Plenty of conversions arrive that shouldn’t pay, and the reward step is where they’re quietly turned away. A duplicate webhook loses the conditional flip and stops. A conversion for a referral the fraud screen has already held — a suspected self-referral, a code caught spraying — never gets past the screen check, and lands in front of a person instead of the ledger (that’s Part 5). A conversion for a friend who was never attributed has no pending referral to flip at all, so there’s simply nothing to pay. And a refund that arrives after a reward doesn’t silently reverse the credit; it’s flagged for a person, because clawing money back from a customer’s account is exactly the sort of decision that should never be automatic. In every one of these cases the default is the same: when in doubt, don’t pay, and tell a human why.
Design rules that shaped the reward
- Only a conversion pays. A genuine, paid, non-refunded first purchase is the trigger — never a click or a sign-up.
- The flip is the guarantee. One conditional write from pending to rewarded makes the payout exactly once, however many times the webhook fires.
- Both sides, one unit. The referrer’s and the friend’s credits are written together, one entry per side, to a ledger that is the source of truth.
- Code credits, the model thanks. The store API and ledger do the crediting; Bedrock only writes the thank-you notes.
- The model never sees the ledger. Amounts come from the record; the model is told them for wording and decides nothing.
- When in doubt, don’t pay. Held, unattributed, duplicate, or post-refund conversions stop or go to a person — never an automatic payout.