Refund Policy
When bids are (and aren’t) refunded
Effective date: September 6, 2026 · blowup.lol
1. Overview
This policy explains exactly when payments for bids onblowup.lol are refunded. The rules are implemented directly in the settlement code (see src/features/bidding/settlement.tsand architecture decision Q4 in docs/architecture.md). This is not a generic template — it reflects what the code actually does.
Important
Default position: bids are non-refundable once settled.If your payment succeeds and the bid is recorded against an active season, you will not receive a refund — regardless of rank outcome, click count, or whether you “changed your mind.”
2. Generally non-refundable
Once a payment is verified by Dodo Payments and the bid is settled against an active season (the season that was current when the checkout session was created), the bid is final. The following are not grounds for a refund:
- Your rank dropped because another creator bid more.
- You received fewer clicks than expected.
- You did not gain subscribers or traffic.
- You bid on the wrong category by mistake.
- You no longer wish to participate.
- The season ended and a new one began (normal rollover).
Settled bids remain on the leaderboard for the duration of the season and are part of the permanent season archive afterward.
3. Auto-refund conditions (architecture Q4)
There are exactly four conditions under which the settlement code automatically initiates a full refund via the Dodo Payments API. These are hardcoded in settlement.tsand are not discretionary:
| Code reason | Trigger | Source |
|---|---|---|
season_rolled_over | The season targeted at checkout is no longer active when the webhook arrives (a new season has started). | settlement.ts:250 |
no_active_season | No active season exists for the category at webhook time (e.g., category disabled, gap between seasons). | settlement.ts:244 |
amount_out_of_bounds | The paid amount is outside the allowed range ($5–$10,000). | settlement.ts:215 |
missing_metadata_or_fields | Required metadata (categorySlug, handle, seasonId) or payment fields (payment_id, checkout_session_id, total_amount) are missing from the webhook payload. | settlement.ts:218-224 |
Policy detail
These four conditions are exhaustive. No other auto-refund paths exist in the code. If your payment does not match one of these, it will not be auto-refunded.
4. Season rolled over (season_rolled_over)
Seasons roll over every Monday at 00:05 UTC via an Inngest cron job. When you start checkout, theseasonIdof the currently active season is embedded in the Dodo checkout session metadata.
If the webhook arrives after that season has ended (i.e., a new season is now active for that category), the code detects the mismatch:
// settlement.ts lines 247-252
if (active.seasonId !== intendedSeasonId) {
throw new Q4Refund('season_rolled_over');
}The payment is fully refunded. The bidder’s money is never applied to a week they didn’t intend to bid for.
Auto-refund triggered
This protects bidders from checkout-to-webhook latency crossing a season boundary. The refund is automatic — no action needed by the bidder.
5. No active season (no_active_season)
If a category has no active season at the moment the webhook is processed (for example, the category was disabled, or there is a gap between the end of one season and the start of the next), the settlement code cannot attribute the bid to any season.
// settlement.ts lines 243-246
const [active] = await tx.select({...}).from(seasons)
.innerJoin(categories, ...)
.where(and(eq(categories.slug, slug), eq(seasons.status, 'active')));
if (!active) {
throw new Q4Refund('no_active_season');
}The payment is fully refunded. This ensures money is never held in limbo without a valid season to credit it to.
6. Amount out of bounds (amount_out_of_bounds)
The allowed bid range is $5 minimum to$10,000 maximum per bid, enforced byassertBidAmountin src/features/bidding/pipeline.ts:
// pipeline.ts lines 58-65
export function assertBidAmount(amountCents: number): void {
if (!Number.isInteger(amountCents)) throw new Error('amountCents must be an integer');
if (amountCents < CUSTOM_BID.MIN_CENTS || amountCents > CUSTOM_BID.MAX_CENTS) {
throw new Error(
'bid must be between 500 and 1_000_000 cents ($5–$10,000)',
);
}
}CUSTOM_BID.MIN_CENTS = 500($5) and CUSTOM_BID.MAX_CENTS = 1_000_000($10,000) are defined in src/config/site.ts.
If a webhook arrives with a total_amountoutside this range, the settlement code treats it as structurally invalid and auto-refunds:
// settlement.ts lines 211-216
if (!structurallyInvalid) {
try {
assertBidAmount(amountCents as number);
} catch {
return refundUnattributable(paymentId, refunds, paymentId, 'amount_out_of_bounds');
}
}Important
The checkout session creation also validates the amountbefore redirecting to Dodo, so this condition should only occur if someone manually crafts a webhook payload or if Dodo’s amount differs from what was requested (which should not happen with the current integration).
7. Unattributable payments (missing_metadata_or_fields)
The settlement code requires the following fields to be present in the verified webhook payload to attribute a payment to a bid:
payment_id(Dodo’s payment ID — the idempotency key)checkout_session_idtotal_amount(numeric)metadata.categorySlugmetadata.handlemetadata.seasonId
If any of these are missing or malformed, the payment is considered unattributable — we cannot determine which campaign, season, or creator it belongs to. Rather than silently keeping the money, the code auto-refunds:
// settlement.ts lines 204-225
const structurallyInvalid =
!paymentId ||
!checkoutSessionId ||
typeof amountCents !== 'number' ||
!slug ||
!handle ||
!intendedSeasonId;
if (structurallyInvalid) {
return refundUnattributable(
paymentId,
refunds,
paymentId,
'missing_metadata_or_fields',
);
}If payment_idis also missing, the event is logged and dropped without a refund (there is no payment ID to refund).
8. Refund process
When an auto-refund condition is met:
- The settlement code calls
refundOrToleratewhich invokes Dodo Payments’ refunds API:refunds.create({ payment_id }). - The refund is idempotent: if the webhook is redelivered (Dodo’s at-least-once delivery) and the refund was already issued, the code tolerates the
charge_already_refundederror and does not throw. - The webhook event is marked
processed_atin thewebhook_eventstable so it is never retried. - The settlement outcome is recorded as
{ kind: 'refunded', reason: '...' }.
Refund timing is determined by Dodo Payments and the card network — typically 5–10 business days to appear on the original payment method. Blowup does not control this timeline.
Policy detail
The refundUnattributablefunction logs every auto-refund with its reason to the server console for audit purposes. No bid row is created for refunded payments.
9. Contact
Questions about a specific refund, or if you believe a refund was missed:
Blowup
Individual operator, India
Email: varshith@blowup.lol
We will respond to valid requests within 30 days. Requests that require significant effort may take up to 90 days with notice.
Legal review disclaimer: This Refund Policy page was written to accurately reflect the actual auto-refund logic in the codebase as of the effective date above. It isnot a substitute for professional legal reviewbefore handling significant real revenue. The operator acknowledges this and plans to engage qualified counsel before any production scale-up.