nvoke

Use case

Stripe webhooks in under 50 lines.

Stripe sends you payment events. You need to turn them into database writes. A Stripe webhook handler on nvoke is one function, one URL, one secret.

import Stripe from "stripe";

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

export default async function (req) {
  const sig = req.headers["stripe-signature"];
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch {
    return new Response("bad signature", { status: 400 });
  }

  switch (event.type) {
    case "checkout.session.completed":
      await markOrderPaid(event.data.object);
      break;
    case "customer.subscription.deleted":
      await markSubscriptionCanceled(event.data.object);
      break;
  }

  return { received: true };
}
A complete Stripe webhook handler.

Setup, end to end

Create a function on nvoke, paste the handler above, add two secrets: STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET. Mark the endpoint as public so Stripe can POST without an API key header. Copy the endpoint URL, open the Stripe dashboard, add it as a webhook endpoint, select the events you care about. Stripe will send a test event — you will see it hit in the log panel within a second.

That is the entire setup. No Vercel project, no Lambda + API Gateway, no ngrok tunnel during development. Local development, if you want it, is a matter of running stripe listen --forward-to against the same URL.

Idempotency, the quiet killer

Stripe will retry a webhook on any non-2xx response, and will sometimes deliver the same event twice for other reasons. Every handler must be idempotent: handling checkout.session.completed twice for the same session must not charge the customer again or send two confirmation emails.

The usual pattern: use event.id as a dedupe key. Either skip handling if you have already seen the ID, or make the downstream side effect idempotent (UPSERT, a unique constraint on event_id in the order table). Both work; the second is more robust under concurrent delivery.

What not to do in a webhook

Do not block the webhook response on slow downstream work. If you need to send a complex email, reconcile against an external system, or do anything that might take more than a few seconds, write the event to a database table and process it from a scheduled nvoke function. Stripe wants a fast 200.

Ship a Stripe handler tonight.

Paste the handler above, add your keys, copy the URL into Stripe. Done before dinner.