Input

709 chars

Output

0 chars

Why Stripe Webhooks Need Their Own Formatting Workflow

Stripe webhooks are the source of truth for billing, subscription, and payment lifecycle events — but their raw JSON arrives minified, with the actual event data buried two levels deep inside data.object. When debugging a failed deploy or unexpected charge, you need a fast way to format and visually scan the payload. This page is pre-loaded with a representative charge.succeeded event so you can see the structure immediately. Replace it with your own captured webhook payload (from your application logs or the Stripe Dashboard's Events log) to inspect what Stripe actually sent.

The Anatomy of a Stripe Event

Every Stripe webhook follows the same envelope structure. Knowing the layout cuts debug time:

  • id — Event ID, format evt_.... Useful for idempotency: if your handler has already processed this event ID, skip it.
  • type — The event type, e.g., charge.succeeded, invoice.payment_failed, customer.subscription.updated. Branch your handler logic on this.
  • api_version — The Stripe API version pinned to your webhook endpoint. Critical when migrating versions — the schema of data.object may change.
  • data.object — The actual resource (Charge, Invoice, Subscription, etc.). This is what you care about 90% of the time.
  • data.previous_attributes — Only present on *.updated events. Shows the field values before the update, so you can detect what actually changed.
  • livemodetrue for live events, false for test mode. Always log this — running test handlers against live data is a classic mistake.
  • request.idempotency_key — Set if the original API call used an idempotency key. Helps trace which user action triggered the event.

Common Stripe Webhook Debugging Tasks

"Why did this charge succeed but not update the subscription?" A successful charge fires charge.succeeded, but the subscription isn't extended until invoice.payment_succeeded fires (or invoice.paid on newer API versions). Check that both events are being processed.

"Did the user upgrade their plan?" Look at customer.subscription.updated with data.previous_attributes.items set. Diff against data.object.items to see what changed.

"What card was charged?" data.object.payment_method_details.card contains brand, last4, and country. Never log the full card number — Stripe doesn't send it, and PCI compliance forbids storing it.

Verifying Webhook Authenticity

Pretty-printing the JSON is just the first step. Production handlers must verify the Stripe-Signature header before trusting the payload. Stripe signs the raw request body with HMAC-SHA256 using your endpoint's signing secret. Verify with the official Stripe libraries, or read the algorithm at Stripe's docs. Don't roll your own verifier — timing-safe comparison is easy to get wrong.

Related Tools

Common Use Cases

Related Articles