charanjit-singh/lovable-skills/stripe-payment-webhooks

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when implementing or fixing Stripe payment webhooks: signature verification, event handlers, syncing subscriptions or Checkout to Supabase, webhook endpoint setup. Not for pricing page UI only, non-Stripe providers, or generic edge functions without payment events (see edge-functions-and-webhooks).

Install

npx vskill@latest install charanjit-singh/lovable-skills/stripe-payment-webhooks

SKILL.md

Stripe payment webhooks

Webhooks are the source of truth for paid state. Checkout success URLs and client callbacks are not sufficient alone.

Related: edge-functions-and-webhooks (hosting), payment-webhook-idempotency, payment-webhook-testing, payment-failed-and-recovery, pricing-and-billing.

Endpoint

  • Supabase Edge Function: supabase/functions/stripe-webhook/index.ts
  • Stripe Dashboard → Webhooks → endpoint URL → signing secret in STRIPE_WEBHOOK_SECRET
  • Use raw body for signature verification (do not parse JSON first).
ts
import Stripe from "npm:stripe@17";
const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!, {
apiVersion: "2024-11-20.acacia",
httpClient: Stripe.createFetchHttpClient(),
});
const cryptoProvider = Stripe.createSubtleCryptoProvider();
const signature = req.headers.get("stripe-signature");
const body = await req.text();
let event: Stripe.Event;
try {
event = await stripe.webhooks.constructEventAsync(
body,
signature!,
Deno.env.get("STRIPE_WEBHOOK_SECRET")!,
undefined,
cryptoProvider
);
} catch {
return new Response("Invalid signature", { status: 400 });
}

Return 2xx quickly after enqueue or DB write; Stripe retries on non-2xx.

Database (service role inside function)

sql
-- Processed events (idempotency) — see payment-webhook-idempotency skill
create table public.stripe_webhook_events (
id text primary key, -- Stripe event id evt_...
type text not null,
processed_at timestamptz default now()
);
create table public.subscriptions (
user_id uuid primary key references auth.users(id),
stripe_customer_id text unique,
stripe_subscription_id text unique,
status text not null,
price_id text,
current_period_end timestamptz,
cancel_at_period_end boolean default false,
updated_at timestamptz default now()
);

Map user_id via client_reference_id on Checkout or metadata.user_id on Customer/Subscription.

Event handlers (subscriptions)

EventAction
checkout.session.completedRead subscription or mode; link customer to user_id; upsert subscription row
customer.subscription.createdUpsert status, price_id, period end
customer.subscription.updatedSame; handle plan change, cancel_at_period_end
customer.subscription.deletedSet status canceled; revoke entitlements
invoice.paidConfirm period; optional receipt email
invoice.payment_failedSee payment-failed-and-recovery
ts
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
const userId = session.client_reference_id ?? session.metadata?.user_id;
if (!userId) break;
// retrieve subscription if mode=subscription
break;
}
case "customer.subscription.updated":
case "customer.subscription.deleted": {
const sub = event.data.object as Stripe.Subscription;
await upsertSubscriptionFromStripe(sub);
break;
}
default:
// unhandled — still return 200 if intentionally ignored
}

One-time payments

For mode: "payment" Checkout or Payment Links, handle:

  • checkout.session.completed (check mode === "payment")
  • payment_intent.succeeded

Store orders or purchases table; do not reuse subscription row without clear schema.

Metadata contract

Always set on Checkout Session creation (server or Stripe Dashboard template):

  • client_reference_id: Supabase auth.users.id
  • metadata.user_id: same (backup)

Never trust webhook to grant access without resolving user from metadata you control at session creation.

Entitlements

After DB upsert, app reads subscriptions.status:

  • Pro: active, trialing
  • Not pro: canceled, unpaid, past_due, incomplete, missing row

Avoid

  • Skipping signature verification in production.
  • Using Checkout success_url alone to set is_pro.
  • Logging full card or customer PII.
  • 4xx on duplicate events (use idempotency — return 200 if already processed).

Checklist

  • [ ] Raw body + constructEvent / constructEventAsync
  • [ ] stripe_webhook_events insert before side effects
  • [ ] Service role client only inside function
  • [ ] Test mode vs live mode secrets separated

Templates: examples.md.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
10
Repository forks
9
7-day trend score
12

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/charanjit-singh/lovable-skills/stripe-payment-webhooks/badge)](https://verified-skill.com/skills/charanjit-singh/lovable-skills/stripe-payment-webhooks)

Something wrong with this skill or its page? Tell us.