Payment Webhook Doctor
Start with the evidence, not a resend. Replaying an event into a broken handler can repeat its side effects.
Route one task at a time
| Problem | Read |
|---|---|
| Signature fails, wrong secret, parsed body, old delivery | references/signatures.md |
| Duplicate credit, delayed fulfilment, failure after success, refunds out of order | references/reconciliation.md |
| 4xx/5xx/timeout, recovery, missing event, production rollout | references/incident-playbook.md |
| Need verified protocol facts and version boundaries | references/sources.md |
| Need a non-Stripe / M-Pesa adapter | references/provider-boundary.md |
Workflow
- Establish scope: provider, snapshot versus thin event, test/live mode, connected account, endpoint framework and API version. Ask only for missing facts that change the diagnosis.
- Inspect handler code, migrations, expected order amount/currency, server-created PaymentIntent mapping, and redacted delivery metadata. Keep signing secrets in local environment variables. Don't ask users to paste secrets or customer bodies into chat.
- Verify the exact raw request bytes before parsing JSON. Prefer the official Stripe SDK for a deployed handler. The bundled dependency-free verifier is a small, inspectable diagnostic reference, not a replacement SDK.
- Use the local tools below to reproduce the fault with synthetic fixtures first. Classify evidence as observed, inferred, or unverified. A valid signature authenticates a delivery; it does not prove the order should be fulfilled.
- Repair the smallest cause and add a regression test. Bind payments to a trusted server-side order; compare amount, currency, account scope and mode. Keep inbox, ledger update, and side-effect intent in one database transaction.
- Verify duplicates, distinct events for the same object, restart, out-of-order delivery, failed events, and partial/full refunds. Do not use event timestamps as a total ordering mechanism.
- Deliver findings, patch, test evidence, unresolved integration requirements, and an explicit rollback/recovery plan. Don't deploy, resend live events, issue refunds, or transfer money without the user's authorized workflow.
Local tools
Run from this skill's root:
npm test
npm run demo
Inspect a saved test-mode fixture using its documented fixture clock:
export DOCTOR_FIXTURE_SECRET='fixture_only_not_a_live_secret'
node scripts/doctor.mjs --body fixtures/payment.json --header fixtures/signature.txt \
--secret-env DOCTOR_FIXTURE_SECRET --fixture-now 1794130000 \
--normalized-out /tmp/payment-doctor-event.json
python3 scripts/ledger.py --db /tmp/payment-doctor-reference.sqlite \
--orders fixtures/orders.json < /tmp/payment-doctor-event.json
The export refuses to overwrite an existing file. Use a fresh output path or remove your own synthetic output deliberately. The Python CLI writes only the explicitly named local SQLite database. It expects the verifier's trusted normalized output; it does not verify signatures itself. Never expose it as a public webhook endpoint.
The normal diagnostic report excludes customer data, raw payloads and signatures. An explicit normalized export retains identifiers and integer amounts needed for local reconciliation. Keep it private and out of version control.
--fixture-now is only for old synthetic test events; it refuses live-mode input. Omit it for an ordinary current delivery. The reference ledger rejects live-mode events, never calls Stripe, and never dispatches outbox rows.
Required report
- Outcome: what happened and whether it is reproduced.
- Evidence: files/lines, sanitized delivery metadata, exact commands, observed results.
- Root cause: confirmed cause versus remaining hypotheses.
- Repair: minimal patch plus migration/backfill implications.
- Regression matrix: duplicate, restart, scope mismatch, wrong amount/currency, ordering, refund cases.
- Operational handoff: test-mode integration evidence still needed; safe replay and rollback criteria.
Boundaries
Support is intentionally narrow: Stripe snapshot payment_intent.succeeded, payment_intent.payment_failed, and refund object events. Thin events, subscription entitlements, Checkout sessions, disputes, multicapture payment semantics, application fees, taxes and Connect transfers need dedicated adapters. A zero-finding result is not a security certification or proof that funds arrived. See the references before extending.