Skip to content
Hidden Logic Lab

Review Stripe webhook handlers for idempotency before merge

Cursor or Claude Code applies a SKILL.md on webhook PRs: signature verification, idempotency keys, replay windows, and dual-write races — with merge-blocking findings.

·Coding·35 min·Working

A webhook-review SKILL.md in payments docs, verified event-id dedup in code or DB, and no handler merged that processes the same charge twice under retries.

  1. 1

    Replay one event twice locally

    Use stripe listen or fixture files. The skill should require a second delivery to be a no-op, not a duplicate ship.

  2. 2

    Align status codes with runbook

    Document when ops should replay from dashboard vs when code must 5xx. The skill’s HTTP rules must match on-call expectations.

  3. 3

    Link from payments README

    Point webhook PR template at this SKILL.md. Billing changes without idempotency review should not merge by default.

The prompt to paste

Write SKILL.md for reviewing Stripe (or similar signed webhook) handlers before merge. Focus on verify-then-process, idempotency, and race-safe side effects — not general API style.

Repository context:
[webhook route path e.g. app/api/stripe/webhook/route.ts]
[how signing secret is loaded — env name only, e.g. STRIPE_WEBHOOK_SECRET]
[persistence: Postgres table, Redis, or queue for processed event ids]
[downstream writes: billing DB, entitlements, email — list systems by name]

PR context (paste when using):
[PR link, changed handler files, new event types handled]

SKILL.md must include:
1. Purpose — Stripe retries webhooks; without idempotency, one payment succeeds twice in your ledger or ships two fulfillment jobs.
2. When to use — any PR touching webhook routes, signature verification helpers, or billing side effects triggered by events. When not to use — dashboard-only Stripe config with no code.
3. Hard rules for findings:
   - Block: missing stripe.webhooks.constructEvent (or equivalent) before parsing body; using parsed JSON before signature check; no durable idempotency on event.id (or idempotency key) before irreversible side effects; trusting client-supplied payment ids without matching event object; processing in wrong order without handling partial failure.
   - Warn: idempotency only in memory; replay window longer than team policy without timestamp tolerance check; dual write (DB + queue) without outbox or single transactional boundary; logging full event payload with PII; returning 200 before work completes so Stripe stops retries on failure.
   - Note: handler file organization — never block merge on style alone.
4. Review steps the agent must execute:
   - Confirm raw body is passed to signature verification (not re-serialized JSON).
   - List each event type handled and side effects per type; mark which are irreversible.
   - Trace idempotency: where event.id is stored, unique constraint or lock, and what happens on duplicate delivery.
   - Check replay: reject or ignore events older than [define window] if policy requires; document clock skew handling.
   - Map dual-write paths: if DB update and external API both run, flag race if not idempotent or not ordered with compensating action.
   - Verify HTTP status: 4xx for bad signature, 5xx for retryable failures, 2xx only after durable idempotency record — per team policy [placeholder].
5. PR comment template: event types table, idempotency mechanism summary, findings list, merge recommendation.
6. Safe patterns section: insert processed_events row in same transaction as entitlement update; use Stripe idempotency keys on outbound API calls where applicable.
7. Two example prompts: add checkout.session.completed handler vs refactor signature parsing.
8. Pitfalls: testing only with stripe trigger CLI once, conflating payment_intent and charge events, deleting idempotency rows too early, swallowing errors with 200 OK.
9. Done means: every Block fixed, idempotency storage named in comment, no webhook secrets in logs or PR text, tests or manual replay steps documented.

Rules:
- Never paste STRIPE_SECRET_KEY or webhook signing secret values.
- Do not invent event types not in the diff or Stripe docs link pasted by human.
- Output only SKILL.md markdown.

Returning 200 before the idempotency row commits invites duplicate side effects on crash. The skill should Block happy-path 200 when work is not durable yet.

Sample case written by Hidden Logic Lab. Not affiliated with Cursor or Claude Code.

More in Coding

Get new cases in your inbox

Weekly notes on new use cases and agent workflows. No product pitches.

Occasional notes on new cases. No product pitches in the first note.