Securing Digital Downloads with Expiring Links

Securing Digital Downloads with Expiring Links

Last Updated: October 21, 2025 6 min read Tags: #cloud architecture #aws #security #system design

Table of Contents


    Revenue leakage is a silent killer for digital products. You launch a premium report or audio course, and within a week, the direct S3 link appears on Reddit or Discord.

    While you cannot stop a determined pirate from re-hosting a file, you can (and must) stop the far more common vector of casual link sharing.

    This occurs because most “security” implementations protect the page, not the asset. They hide a permanent link behind a login wall. Once that link is exposed, authentication becomes irrelevant.

    When I started building out the premium content library for GetViajo.com, my personal R&D lab for growth systems, I knew this was a critical, foundational problem to solve. A simple “members-only” page wasn’t enough.

    My architectural solution was to shift from linking to a file to brokering a secure transaction. I built a “Digital Vault” system that never exposes the origin URL. Instead, it treats every download request as a unique security event.

    Here’s the architectural blueprint for how I engineered it and, more importantly, the business decisions that drove the design.

    The fundamental flaw in most systems is that they protect the page, not the asset.

    A typical setup looks like this: a user logs in, visits /my-premium-content, and clicks a button that links to .../assets/my-secret-ebook.pdf. Even if that URL is long and unguessable, the moment one paying customer copies and shares that link, the asset is free for the world.

    My guiding principle for this build was: Never link to the file. Ever.

    I built a “gatekeeper” API endpoint that intercepts every download request. Applying the same architectural rigor used in my full-funnel marketing intelligence system, this endpoint validates two distinct conditions before serving a single byte:

    1. Who are you? (Authentication)
    2. Are you allowed to be here? (Authorization)

    Only after verifying “yes” to both does the system generate a temporary, single-use “key” and hand it to the user.

    A diagram showing a user request being validated by an API gatekeeper before being granted access to the secure S3 vault The core architectural principle: I don’t give users a key to the vault. I have a gatekeeper who validates them and then fetches the content for them.

    The Architecture: Three Components of the Digital Vault

    This “gatekeeper” API is the core of the system. I built it using a serverless function that integrates three cloud services to work in perfect concert.

    1. The “Bouncer”: Resourceful Authorization

    The Engineering Constraint: Building a bespoke authorization system (custom tables for permissions, payment status syncing) introduces significant complexity and maintenance overhead. The goal was to achieve robust security without incurring technical debt.

    The Solution: I utilized Stripe as the source of truth for purchase validity. When a transaction succeeds, Stripe fires a secure webhook. My backend validates this payload and executes a server-to-server call to PostHog, setting the is_premium: true property. The client is never involved in this chain, making the status impossible to spoof.

    Technical Detail: The first step is authenticating the user’s identity, which is handled by my site’s core session management. Once the API confirms who the user is, its next job is to determine what they can access. It takes the user’s verified email and queries the PostHog API, checking for the is_premium: true property—a flag that, as established, can only be set by my secure backend.

    The Outcome: This architecture leverages existing infrastructure (PostHog) as a secure “key store,” eliminating the need for a custom permissions database. It delivers robust authorization with zero additional maintenance overhead.

    2. The “Expiring Key”: AWS S3 Pre-Signed URLs

    Business Problem: Once a user is authenticated, how do I give them the file without giving them the source link?

    My Solution: I used one of the most powerful and effective features of AWS S3: pre-signed URLs.

    Technical Detail: After the PostHog check passes, the API uses the AWS SDK to generate a unique, temporary URL for the requested file. I configured this URL to expire after 600 seconds (10 minutes). This link grants temporary GetObject permission for that specific file and nothing else.

    Business Outcome: This is what directly protects the revenue. A user can download their file, but if they copy and share that pre-signed link, it’s completely useless 10 minutes later. This effectively neutralizes casual link-sharing.

    3. The “White Glove” Delivery: A Focus on User Experience

    The UX Challenge: Secure backend systems often degrade the frontend experience, serving files with cryptic internal filenames (e.g., ASSET_001-v3-FINAL.pdf) that confuse the end user.

    My Solution: I used the API to ensure the experience felt premium, not clunky. I stored the “human-friendly” metadata for each lesson (like the real title) in a DynamoDB database.

    Technical Detail: When the API generates the expiring link, it also fetches the file’s real name from DynamoDB. It then sets the Content-Disposition header in the API response.

    Business Outcome: When the user clicks download, their browser doesn’t see PT-EU-001.pdf. It sees At The Café - Ordering From a Café in Portugal (Cheat Sheet).pdf. This is a small, but critical, piece of engineering. It proves that you don’t have to sacrifice user experience for security. You can (and should) do both.

    [!tip] Never Underestimate a Good Filename The Content-Disposition header is one of the most overlooked tools for improving user experience in a secure system. It’s a single line of code that takes the user from “what is this cryptic file?” to “ah, exactly what I wanted.” This builds trust and makes the product feel more polished.

    Stay in the loop

    Sign up for my newsletter to get my latest updates directly in your inbox.

    By clicking Sign Me Up, you confirm you are 16+ and agree to our Terms of Use and Privacy Policy.

    A flowchart of the Digital Vault's 5-step, serverless API logic The complete, end-to-end runtime flow. This API queries PostHog for the user’s premium status, which is securely set via a server-to-server webhook from Stripe, ensuring the authorization check is based on a trusted source.

    How This Applies to a Growth Team

    These “Digital Vault” principles apply to any organization managing high-value digital assets, from SaaS platforms to enterprise research firms.

    • Audit Your Asset “Vault”: Where is your premium content actually stored? Ask your team for the direct links. If it’s just an unguessable S3 URL or a file in a password-protected WordPress folder, you have a revenue leak, not a vault.
    • Run the “Forward Test”: Get the direct link to your most valuable asset. Email it to a personal account. Now, forward that email to a friend. If they can open it, you don’t have a secure system.
    • Audit Your Vendors: That third-party course platform you use? Find out how they secure your files. Are they using expiring links, or is the platform itself the source of the leak?

    Future-Facing Questions & Next Steps

    [!faq]- How would this system scale to serving video or streaming content? The architectural principle is exactly the same, but the AWS service would change. Instead of S3 pre-signed URLs for a single file, the “gatekeeper” API would generate temporary access tokens for a service like AWS MediaConvert or a secure CloudFront distribution. The user would be granted time-limited permission to stream the content, but never to access the source video file.

    [!faq]- What if a user’s 10-minute link expires before they click it? This is a key part of the user experience design. The “download” button on the website doesn’t link anywhere. It triggers a function that calls the “gatekeeper” API. This means every time a user clicks the button, they are always running the full authentication and link-generation process. If a link expires, they can simply go back to the page and click the button again to get a fresh, valid one. The user never even knows the links are temporary.

    Photo of Justin Borge

    By Justin Borge

    Marketing Growth Engineer. I design integrated systems (analytics, content, AI) to solve marketing chaos. This website is my 'backstage pass,' documenting what I'm working on, implementing, and learning.