DEV Community

Joey
Joey

Posted on • Edited on

How to Build a Stripe Webhook That Delivers Digital Products Automatically

You just made a sale. Your customer paid. Now what?

If you're manually emailing download links, you're leaving money on the table. Automation scales you without scaling your workload. Here's how to build a Stripe webhook that delivers your product the moment payment completes.

The Setup

We'll use:

  • Stripe (payment processor)
  • Netlify Functions (serverless backend)
  • Resend (transactional email)

If you're using a different host (Vercel, AWS Lambda, etc.), the logic is identical — only the deployment changes.

Step 1: Create Your Webhook Endpoint

Your webhook is a simple HTTPS endpoint that Stripe calls every time something happens. Here's the bare minimum:

// netlify/functions/stripe-webhook.js
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const { Resend } = require("resend");

const resend = new Resend(process.env.RESEND_API_KEY);

exports.handler = async (event) => {
  const sig = event.headers["stripe-signature"];
  const body = event.body;

  let stripeEvent;

  try {
    stripeEvent = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return {
      statusCode: 400,
      body: `Webhook Error: ${err.message}`,
    };
  }

  // Handle the charge.succeeded event
  if (stripeEvent.type === "charge.succeeded") {
    const charge = stripeEvent.data.object;

    // Extract customer email
    const email = charge.billing_details?.email || charge.receipt_email;

    // Send download link email
    try {
      await resend.emails.send({
        from: "no-reply@yourdomain.com",
        to: email,
        subject: "Your Download is Ready",
        html: `<p>Thank you for your purchase!</p><p><a href="https://yourdomain.com/downloads/your-product.zip">Download Your Product</a></p>`,
      });

      console.log(`Email sent to ${email}`);
    } catch (emailErr) {
      console.error("Email failed:", emailErr);
      return {
        statusCode: 500,
        body: "Email delivery failed",
      };
    }
  }

  return {
    statusCode: 200,
    body: JSON.stringify({ received: true }),
  };
};
Enter fullscreen mode Exit fullscreen mode

Step 2: Deploy It

Push to your Git repo. Netlify auto-deploys. You now have a live endpoint like:

https://yourdomain.netlify.app/.netlify/functions/stripe-webhook
Enter fullscreen mode Exit fullscreen mode

Step 3: Register with Stripe

  1. Go to Stripe DashboardDevelopersWebhooks
  2. Click Add endpoint
  3. Paste your endpoint URL
  4. Select charge.succeeded event
  5. Copy the Signing Secret (starts with whsec_)

Step 4: Add Your Secrets

Add these to your .env.production:

STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
RESEND_API_KEY=re_...
Enter fullscreen mode Exit fullscreen mode

Step 5: Test It

Create a test charge on your Stripe dashboard. Check your email — the download link arrives instantly.

What About Failures?

Stripe retries webhooks automatically if your endpoint returns a 5xx error. Always:

  1. Log everything — exceptions, email failures, missing data
  2. Return 200 even if something fails — then handle the error separately
  3. Set up alerts — if emails fail, you need to know immediately

A simple Slack notification:

if (emailErr) {
  await fetch(process.env.SLACK_WEBHOOK_URL, {
    method: "POST",
    body: JSON.stringify({
      text: `❌ Email delivery failed for ${email}: ${emailErr.message}`,
    }),
  });
}
Enter fullscreen mode Exit fullscreen mode

Common Issues

"Webhook secret invalid"

  • Make sure you're using the right secret from Stripe (different in test vs. live mode)

"Email not sending"

  • Check Resend API key is valid
  • Verify your domain is authorized in Resend
  • Check spam folder (it will arrive there until you verify your domain)

"Customer email is null"

  • Stripe doesn't always populate billing_details.email
  • Use receipt_email as fallback (that's what we do above)
  • Or store the customer email in Stripe metadata when creating the charge

Next: Scale It

Once this works, you can:

  • Personalize emails — include the customer's name, order amount, etc.
  • Track delivery — log successful sends to a database
  • Deliver different products — look up which product they bought and send the right link
  • Offer a discount — attach a coupon code to their next purchase email

The Payoff

Now when someone buys your product at 3 AM, they get their download within seconds. You're asleep. Your system is working.


Need help? Drop a comment. I answer every one.

Build something cool with this? Let me know — I feature the best implementations.


🛒 Check Out My Products

If you're building AI agents or digital products, these might help:

See all products: https://joeybuilt.gumroad.com

Top comments (0)