Skip to main content
One idea at a time

Reconciliation asks what is true now

A cancellation event and an earlier subscription-update event reach Taskboard in reverse order. Treating both event payloads as instructions would make arrival order determine paid access. Reconciliation instead asks Stripe for the subscription's current state.

An accepted event records a reason to reconcile. The worker locates the subscription through trusted provider relationships, fetches the provider record, translates its status and price into Taskboard's supported plan, and updates the local subscription.

The provider is authoritative for billingโ€‹

Taskboard is authoritative for tasks and memberships. Stripe is authoritative for its subscription lifecycle. A local subscription row is an application projection of that external state.

Illustrative reconciliation
Received event: an earlier subscription update
Current provider state: canceled
Saved local entitlement: free plan

The older event still triggers useful work. The worker does not restore the old snapshot because the provider's current state says the subscription ended.

Remote reads can race tooโ€‹

Worker A fetches active, then stalls. Stripe cancels the subscription. Worker B fetches canceled and stores it. If A resumes and writes its old result unconditionally, the local row becomes wrong again.

Taskboard takes a Postgres advisory lock for the workspace to serialize reconciliation. Accepted relevant webhooks also increment a billing revision. After the provider read, the update checks the revision it observed before the call. If a newer webhook changed that revision, the worker schedules another reconciliation instead of writing the stale result. The rule covers the local write, not just the initial job claim. Two different jobs may target the same workspace.

The expected outcome is that an obsolete claimant cannot overwrite a result written under newer ownership. A failed fenced update means the worker needs to defer to current work, not force its old data into the row.

Recovery extends beyond webhook arrivalโ€‹

Taskboard's worker checks for stale billing records every minute. Records with a known Stripe customer and no recent sync become eligible after an hour, unless an unfinished billing job already exists. The last confirmed paid entitlement remains effective for at most seventy-two hours without a successful sync. This periodic sweep helps recover missed events. Exhausted jobs still need diagnosis, and the provider query currently examines at most one hundred subscriptions for a customer. Larger customer histories need pagination before relying on this reconciliation policy.

Read the canonical subscription fetch and write conditions in Read api/src/billing.ts and Read api/src/jobs.ts.

Take away

An event prompts a check. Reconciliation stores current provider truth and needs protection against stale remote reads.

Why not trust the browser to report that a subscription was canceled?

The browser is controlled by the user and can be offline. Only a verified provider relationship and provider state can establish the subscription outcome.