Grant a durable download entitlement from verified server-side payment state, make fulfillment safe to repeat and deliver only an authorized private file.

A paid PDF website needs more than a successful checkout and an email containing a file URL. It needs an answer to four questions: was this order really paid, which file did it buy, who may download it and how does delivery recover when something fails?
A hosted checkout can handle payment collection while your application handles those responsibilities. This guide uses Stripe Checkout as a documented example of the payment-confirmation boundary. The private-file and order design is an architectural recommendation, not a feature automatically supplied by the payment page or drop-in implementation code.
Separate payment, entitlement and delivery
Give these three states separate records. Payment state comes from the payment provider. An entitlement is your durable record that an order is allowed to access a particular product or version. Delivery state records whether the buyer was shown a download or sent a notification.
| State | Example question | Why it stays separate |
|---|---|---|
| Payment | Has this specific order reached the accepted paid state? | Opening a success page does not prove payment. |
| Entitlement | Which PDF and version may this order access? | A generic receipt must not unlock every file. |
| Delivery | Was the notification sent and can access be recovered? | Failed email should not charge the customer again. |
For a fictional shop selling a gardening workbook, store a stable internal product identity and an explicit mapping to its permitted checkout price and private file version. Keep that mapping on the server. Do not accept a browser-supplied price, file path or “paid=true” field as authority to release the workbook.
OWASP’s Transaction Authorization guidance supports enforcing transaction decisions and significant data on the server, with controlled state transitions. Here, that means the application owns the relationship between the verified purchase and the right download.
Use the success page for communication, not proof
A buyer may close their tab after paying, lose connectivity or never return from checkout. Your fulfillment must still be recoverable. Conversely, a visitor can directly open a public success-page URL without having bought anything.
Stripe’s fulfillment guide uses webhooks and server-side retrieval of the Checkout Session and line items. The fulfillment function checks payment_status and must tolerate multiple, even concurrent calls for the same Session. Delayed-notification payments can complete Checkout before the payment succeeds; checkout.session.async_payment_succeeded covers the later success.
For a paid-only PDF, require the accepted paid state before granting access. If you later support a genuinely free promotion, define that as a separate reviewed policy instead of silently treating every completed session as paid. Keep failed and still-processing payments out of the paid-download path.
The return page can show a pending message and call a server-controlled status route. If it accelerates fulfillment, use the same guarded routine as the webhook. Possession of a Session identifier alone should not give an arbitrary visitor another customer’s order details or file.
Verify the incoming event before trusting it
Stripe’s webhook documentation requires signature verification using the original raw request body, Stripe-Signature header and the relevant endpoint’s signing secret. Parsing and rebuilding the body before verification can break the signature. A signing secret is different from the API key used for server requests.
Reject invalid signatures without processing the event. Keep secrets in the application’s supported secret storage, outside browser code, URLs, logs and committed files. Subscribe only to events the integration actually handles, and use the correct test or live environment when looking up the payment.
Document the event route and its owner. A generic form endpoint with a field called “payment confirmed” is not equivalent to an authenticated provider event. Likewise, a successful signature establishes the event’s authenticity, not that every product or state in it is eligible for your fulfillment rules.
Make fulfillment safe to repeat
Different duplication problems need different guards. Event deduplication recognizes a repeated event identifier. Business idempotency prevents the same purchase granting the same entitlement twice, including when two different events or a return-page request concern it. An API idempotency key for an outbound request is another mechanism; it does not replace your database’s business constraint.
Use a stable purchase identity such as the provider account, environment and Session identity. Enforce the intended uniqueness atomically in durable storage. Checking a flag and then writing it in two separate unprotected operations can let concurrent workers both proceed.
The following is explanatory pseudocode, not SDK code. Its atomic commit and durable queue are responsibilities to implement and review in your selected database and runtime.
ON AUTHENTIC PROVIDER EVENT:
durably record or queue the relevant Session identity
acknowledge only after durable acceptance
FULFILL THE QUEUED PURCHASE:
retrieve current Session and purchased items server-side
verify account, environment, paid state and allowed purchase
map approved product identity to its private file version
atomically grant the unique purchase entitlement
durably queue any notification without duplicating the grant
retry unfinished delivery separately from payment collection
Validate the expected items, quantities, currency and applicable pricing policy rather than granting whatever a browser claimed to order. Allow legitimate discounts or taxes only through the approved server configuration. Keep an unexpected purchase in an explainable review state instead of delivering a guessed file.
Acknowledge durable work and recover failed delivery
Do not hold the provider connection open while generating a large document or waiting for email delivery. Verify and durably accept the event, then process longer work through a reliable background path. If durable acceptance fails, do not acknowledge success and discard the purchase.
Stripe’s endpoint-management guidance covers failed deliveries, retries and missed-event recovery. Its signing-secret troubleshooting also distinguishes an endpoint’s secret from a CLI forwarding secret. Document the actual endpoint and environment so a test secret is not mistaken for its live counterpart.
Keep payment, entitlement and notification errors individually visible to the operator. Replaying an authentic event should retry unfinished work safely, not send the buyer through checkout again. Add reconciliation for paid purchases lacking an entitlement, and entitlements whose notifications remain unsent. Do not rely on the provider retrying forever.
Keep paid files behind a real access decision
OWASP’s Authorization guidance recommends denying access by default and checking permission for protected resources. A paid PDF in a public media folder remains publicly reachable even if its landing page is hidden or marked noindex.
Store paid originals privately and authorize each download against the appropriate entitlement. Depending on the chosen storage service, the server can stream the authorized file or issue a short-lived signed download URL. Check the service’s actual expiry and revocation behavior; do not assume all signed URLs become invalid immediately when your order record changes.
A signed URL is often a bearer capability: someone possessing it may use it until its restrictions stop them. Choose a bounded lifetime, avoid exposing it in analytics or shared logs, and provide a verified route to request fresh access. Do not trust an email address typed into a public recovery form as proof of purchase ownership.
These controls reduce unauthorized access to the hosted original. They cannot stop a legitimate buyer copying a PDF after downloading it. Explain licensing and permitted use clearly without promising technical copy prevention you do not provide.
Define refunds, revisions and access recovery
Decide whether a purchase covers one file version or future updates, and record that entitlement explicitly. Replacing a file under the same storage key can unintentionally change what previous buyers receive. Keep a controlled version map and a tested replacement procedure.
Handle a refund as a payment event and a business-policy decision about future access, rather than merely changing a local label to “refunded”. Determine the actual provider result, record the decision and apply the supported access change. Already downloaded copies cannot be recalled, and existing signed URLs may remain usable according to their service’s rules.
For failed email, provide an operator or buyer recovery route based on verified ownership. A successful payment receipt and a usable download notification are different outcomes; monitor both without logging private files, complete event payloads or customer secrets.
Test the failure routes before accepting real purchases
Use the provider’s supported sandbox with isolated test products, files, credentials and notification destinations. Never turn these scenarios into unapproved real charges. Keep a small result record for each route.
| Scenario | Expected result |
|---|---|
| Buyer pays but never returns | Authentic server events still grant the correct entitlement. |
| Completed checkout with payment pending | No paid file until an accepted paid state is verified. |
| Forged or modified event | Rejected without entitlement or disclosure of secrets. |
| Repeated and concurrent notifications | One business grant; safe repeat processing. |
| Events arrive out of order | Current verified state and allowed transitions govern the outcome. |
| Database, queue or email interruption | Unfinished work remains recoverable without another charge. |
| Wrong product or unauthorized order lookup | No unrelated file or buyer information is exposed. |
| Expired link or applicable access reversal | A clear denied/recovery state follows the documented policy. |
The practical goal is a traceable purchase that grants exactly the intended access and survives a broken tab, delayed payment or failed notification. Payment collection, private storage and reliable delivery remain separate pieces that must be verified together.
Sources and verification
Editorial review 2026-10-14. Source check dates are recorded separately. Notes describe what each source supports and its limits.
View sources
- Stripe: Fulfill Checkout ordersChecked Server-side payment status, retrieved line items, repeated/concurrent fulfillment and delayed-payment events.
Limits: Provider fulfillment reference; it does not automatically implement private file hosting or the order database. - Stripe: WebhooksChecked Authenticity verification using raw request body, endpoint signature and secret; duplicate and ordering considerations.
Limits: Exact SDK and deployment configuration must follow the version selected for the actual application. - Stripe: Manage webhook endpointsChecked Delivery failures, retries, missed-event recovery and signing-secret distinctions.
Limits: Recovery still requires a correctly implemented durable handler and reconciliation process. - OWASP: AuthorizationChecked Deny by default and authorize access to protected resources.
Limits: General security guidance, not a certification of the proposed application. - OWASP: Transaction AuthorizationChecked Server-controlled transaction data and allowed state transitions.
Limits: Used for architectural principles, not a requirement to copy a banking authorization interface.