# Keep payments and product entitlements consistent

Canonical page: https://yishto.com/en/learn/product-building/payments-entitlements-and-webhooks/

Author: [Yishto](https://yishto.com/en/about/)

Model payments, subscriptions, and access separately, handling signatures, duplicates, ordering, and reconciliation with explicit boundaries for refunds, cancellation, and testing.

Published: 2026-11-10
Updated: 2026-11-10

A user returns from a subscription page before the server receives a payment result. The UI can show confirmation in progress, but a redirect cannot grant paid access. Billing must connect payments, subscription state, and product entitlements while handling duplicates, ordering, and outages.

Only synthetic events E-1 and E-2 are used. The local ledger has invented monotonic revisions; these are not Stripe event-order fields. No accounts, cards, or funds are involved.

## Separate payments, subscriptions, and access

Initiation, authorization, settlement, and product availability are distinct stages whose details depend on payment methods. Subscriptions connect invoices, attempts, and periods; entitlements define usable features. One boolean cannot express every business condition.

Map product users to payment customers, subscriptions to plans, and plans to features through authenticated server logic. Do not let browsers select another customer's ID. Validate products, amounts, and currencies against trusted rules.

An active subscription does not necessarily establish that all invoices were paid. Trials and delayed confirmation have additional conditions. Grant access according to explicit policy and verified objects, checking the relevant API version and documentation.

## Let return pages show progress

Redirects are navigation and may be revisited or forged. The return page queries the product server for its linked transaction or entitlement. Unconfirmed client parameters must not become payment evidence.

Handle duplicate checkout requests with stable business identifiers and appropriately scoped provider idempotency. After an unknown result, inspect existing objects before creating another subscription.

Provide pending, failure, retry, and support paths. Investigate through object references and internal records without collecting complete payment credentials in logs or messages.

## Verify webhooks before processing

Stripe verification uses the raw body, signature header, and endpoint signing secret according to the official SDK. Parsing and reserializing may alter bytes. HTTPS and signature checks protect different boundaries.

Apply timestamp tolerance and replay handling according to the SDK. Separate test and live credentials and endpoints. The ledger assumes trusted verification; this lesson provides no untested framework handler or credentials.

Persist accepted events and pending work before acknowledging promptly, then perform complex work in a recoverable background process. Acknowledging before persistence risks loss; waiting too long can cause duplicate delivery.

## Handle duplicates and ordering independently

Stripe does not guarantee delivery in generation order. Distinct events can share a seconds-level created timestamp, so it is neither a unique deduplication key nor a reliable global order. Track event IDs and reconcile authoritative subscription or invoice objects where needed.

This example assumes the local application already assigned increasing revisions for one entitlement state. It does not model Stripe ordering.

```python
state = {"version": 0, "active": False}
seen = set()
def apply(event_id, version, active):
    if event_id in seen:
        return False
    seen.add(event_id)
    if version <= state["version"]:
        return False
    state.update(version=version, active=active)
    return True
assert apply("E-2", 2, True)
assert not apply("E-2", 2, True)
assert not apply("E-1", 1, False)
assert state["active"]
print(state)
```

The actual output is `{'version': 2, 'active': True}`. E-2 applies, its duplicate is ignored, and older E-1 cannot replace the newer state. Active is a teaching entitlement boolean, not a direct Subscription.status copy.

Real handling needs transactions for event records, state changes, and derived work, plus concurrency and retries. Related objects may lack a shared revision. Older notifications require authoritative reconciliation rather than blindly applying this numeric rule.

## Define access after failures and refunds

A payment failure may trigger a defined grace period or suspension according to product commitments. Period-end cancellation, immediate cancellation, and trial expiry differ. Retain entitlement intervals and reasons.

Refunding a payment and cancelling a subscription are separate actions. A refund does not imply automatic cancellation of future billing or every entitlement. Define full and partial refund effects on past and future use and verify payment and subscription objects separately. Disputes also need their own process.

Invalidate access caches according to policy and enforce current entitlements on the server. Hidden buttons are insufficient, and stale sessions must not preserve revoked capabilities indefinitely. Billing access and data authorization have related but distinct boundaries.

## Bound conclusions from test environments

Stripe sandboxes and test mechanisms support selected scenarios. Test clocks and simulations have limits and do not establish every real payment method, bank delay, or delivery sequence.

Include success, failure, additional-action, duplicate, out-of-order, missed-notification, and reconciliation cases. Keep environment identifiers and signing configurations separate. Never use real cards or funds in this exercise.

A reconciliation job compares local access with authoritative objects and records discrepancies, repair sources, and outcomes. Unknown objects must not automatically receive access. Taxes, pricing, and operating requirements need actual business context.

After processing failure, retain the verified event and retry count instead of creating a new grant on every delivery. Resume from the failed step and reconcile authoritative objects. Manual repairs also need reasons and sources; directly changing a boolean to restore access leaves its expiry and billing basis unexplained.

## Exercise: specify consistent billing states

Document relationships among browser state, payment records, subscriptions, and entitlements. Simulate E-2 before E-1, duplicate events, invalid verification, restart, and refund. Define allowed features and recovery for each.

Accept it when redirects cannot grant access, invalid signatures stay outside processing, duplicates cannot grant twice, and old notifications cannot blindly replace verified state. Refund and cancellation need separate rules. Explain the teaching revision and real reconciliation authority. Offline assertions prove local ledger logic; signatures, transactions, and payment methods need isolated integration tests.

## Sources & further reading

- [Receive webhook events](https://docs.stripe.com/webhooks)
- [Test payments and integrations](https://docs.stripe.com/testing)
- [Subscription lifecycle](https://docs.stripe.com/billing/subscriptions/overview)

Sources checked on 2026-11-10. Examples are teaching data, not production measurements.

## Related guides

- [Use database constraints to protect business correctness](https://yishto.com/en/learn/service-data/database-indexes-and-transactions/)
- [Project: an inventory reservation and fulfillment service](https://yishto.com/en/learn/project-labs/lab-inventory-reservations/)
