Once expanded to production-safe logic, it became 19 steps.
First 6:
Verify webhook signature (STRIPE_WEBHOOK_SECRET)
Extract event.id, subscription.customer, price.id
Ensure price.id === pro_annual_price_id
Check processed_events for event.id
Insert event.id before side effects
Fetch full customer via Stripe SDK
Snippet:
Stripe customer.subscription.created → verify → idempotency → Postgres → provision workspace → SendGrid → Slack → monitoring.
Once expanded to production-safe logic, it became 19 steps.
First 6: Verify webhook signature (STRIPE_WEBHOOK_SECRET) Extract event.id, subscription.customer, price.id Ensure price.id === pro_annual_price_id Check processed_events for event.id Insert event.id before side effects Fetch full customer via Stripe SDK Snippet:
Js id="j3m4zl"
if (!Stripe.verifyWebhookSignature(req.body, signature, process.env.STRIPE_WEBHOOK_SECRET)) { return { success: false, error: 'Invalid webhook signature' }; }
const [existingEvent] = await PostgreSQL.query( 'SELECT * FROM processed_events WHERE id = $1', [eventId] );
if (existingEvent) { return { success: true, message: 'Event already processed' }; } Generated structure: Id="s2x9pq"
my-workflow/ ├── integration.js ├── scripts/ ├── .env.example ├── apcher.lock ├── github/ └── README.md
The surprising part wasn’t the API calls — it was how much production scaffolding each “simple webhook” really needs.
Curious how others here handle idempotent webhook pipelines in production.