Recipe · Stripe

Get notified when a Stripe payment fails

Stripe sends its own webhook payload shape, so a small relay verifies the signature and forwards a clean title/message to your phone.

Stripe webhooks POST Stripe’s own event shape — { "type": "invoice.payment_failed", "data": { "object": {...} } } — not something you can point at a Paperplane endpoint directly. The honest recipe is a small relay: receive the Stripe event, verify its signature, forward a Paperplane-shaped payload.

The relay

import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export default async function handler(req, res) {
  const signature = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(req.rawBody, signature, process.env.STRIPE_WEBHOOK_SECRET);
  } catch {
    return res.status(400).send('Invalid signature');
  }

  if (event.type === 'invoice.payment_failed') {
    const invoice = event.data.object;
    await fetch('https://<host>/events/<username>/Billing', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        title: 'Payment failed',
        message: `${invoice.customer_email ?? invoice.customer} — $${(invoice.amount_due / 100).toFixed(2)}`,
        priority: 'High',
        nature: 'error',
        dataJson: JSON.stringify({ invoiceId: invoice.id }),
      }),
    });
  }

  res.status(200).send('ok');
}

Verifying stripe-signature matters here specifically: this endpoint is publicly reachable, and skipping verification means anyone who finds the URL can send you a fake “payment failed” page.

Add a resolve button

If you refund or retry the charge from a dashboard you host, attach a callback so the notification can trigger that directly — see the API contract for the shape, and cron/systemd for a simpler starting point if webhook signing feels like overkill for now.