Webhooks

Stashly uses Stripe webhooks for billing events. Configure your webhook endpoint in the Stripe dashboard and verify incoming events using the Stripe signature header.

ℹ

Stripe-powered billing webhooks

Stashly billing is powered by Stripe. Webhook payloads come directly from Stripe and are forwarded to your registered endpoint.

Stripe Webhook Endpoint#

POST/webhooks/stripe

Receive Stripe events. Stashly verifies the Stripe-Signature header before processing.

⚠

Raw body required

Stripe signature verification requires the raw (un-parsed) request body. Do not use JSON body-parser middleware on this route.

Verified Events#

Stashly processes the following Stripe events:

EventDescription
checkout.session.completedA new subscription checkout completed
customer.subscription.updatedSubscription plan changed
customer.subscription.deletedSubscription cancelled or expired
invoice.payment_succeededPayment processed successfully
invoice.payment_failedPayment failed — tenant enters grace period

Verifying Webhook Signatures#

All incoming webhooks include a Stripe-Signature header. Verify it using the Stripe SDK:

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

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["stripe-signature"];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case "invoice.payment_succeeded":
      // handle success
      break;
    case "customer.subscription.deleted":
      // handle cancellation
      break;
  }

  res.json({ received: true });
});

Webhook Payload Format#

Stripe event payloads follow the standard Stripe event structure:

{
  "id": "evt_01j9xk2m",
  "type": "invoice.payment_succeeded",
  "created": 1748476800,
  "data": {
    "object": {
      "id": "in_01j9xk2m",
      "customer": "cus_abc123",
      "amount_paid": 2900,
      "currency": "usd",
      "status": "paid"
    }
  }
}

Refer to the Stripe documentation for the full event object schemas.