Two events, no delivery order
Applies to The Fabrica v0.1.2. Documentation source:
1ee1bc9cb661, including post-release documentation corrections.
The two no-trial first purchases this guide was written from each produced three events. One of those runs looked like this:
occurred_at event_type status
2026-…T09:14:51.833881 subscription.created delivered
2026-…T09:14:51.833881 subscription.activated delivered
2026-…T09:14:52.252546 transaction.completed deliveredRead that first column carefully. In both executed runs
subscription.created and subscription.activated carried the identical
occurred_at and were delivered concurrently — and across the two
payments they arrived and were processed in opposite orders, created
first on one and activated first on the other.
Two runs are not a contract, so do not read the timestamps above as one.
What they do establish is the part that matters: these events can arrive
concurrently, and no delivery order should be assumed. Your own account,
plan configuration and trial settings may produce a different sequence
again. A handler that assumes created lands first is relying on something
nobody has promised it; see
What this run fixed.
What this run fixed
Driving this against a real Paddle sandbox exposed one product defect that no test and no amount of reading had caught.
Concurrent first-purchase deliveries failed one of their own webhooks.
_upsert_subscription_row read the subscription by paddle_subscription_id
and inserted when it found nothing. In the first executed run
subscription.created and subscription.activated arrived concurrently, so
both requests read “no row” before either committed, and the loser violated
uq_subscriptions_paddle_subscription_id:
subscription.activated failed IntegrityError: duplicate key value violates
unique constraint
"uq_subscriptions_paddle_subscription_id"The system converged anyway — but only because Paddle retried 20 seconds later. Nothing guarantees that retry once delivery attempts are exhausted, and until it lands the losing handler’s own sync is simply not applied.
The insert now runs inside a SAVEPOINT, so a collision costs only the nested transaction — not the webhook archive and idempotency rows already written in the same session — and the handler re-reads and falls through to the update path on the winner’s row. The second real payment, made against the fixed code, processed all three events on first delivery with zero failures — and did so with the arrival order reversed, which is the case the original code would also have failed.
Provenance. Documentation source: 1ee1bc9cb6619c19f57766731e7884b37f515dc9 (main).
Extracted from docs/paddle-checkout.md.
That commit is later than the release these pages describe: it carries documentation corrections made after thefabrica-v0.1.2 went out. Nothing that changes how the product behaves landed between the two, which is what makes naming that version honest.