Payments & Money APIs · Webhooks, Orchestration & Recurring Payments

Payment Webhooks Done Right

Verify, dedupe, handle out-of-order events and re-fetch.

The PSP tells you what happened via webhooks: payment succeeded, failed, refunded, disputed, payout paid. Handle them like a pro: verify signatures on the raw body, dedupe by event ID, ack fast (2xx) and process via a queue, expect at-least-once and out-of-order delivery (e.g. charge.refunded before payment_intent.succeeded is processed), re-fetch the object from the PSP API for current state when order matters, and run a backfill job that lists recent events/objects to catch anything missed during outages.

Webhook handling

Concern Solution
Authenticity Signature verification over raw body + timestamp tolerance
Duplicates Processed-events table keyed by event ID
Ordering Re-fetch object state; state-machine transitions ignore stale events
Slow processing Ack quickly, process asynchronously
Missed events (outage) Periodic reconciliation via list endpoints
Endpoint rotation Multiple secrets during rotation
@router.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
    raw = await request.body()
    try:
        event = stripe.Webhook.construct_event(raw, request.headers["stripe-signature"], ENDPOINT_SECRET)
    except (ValueError, stripe.error.SignatureVerificationError):
        raise HTTPException(400)
    inserted = await db.execute(
        "INSERT INTO processed_events(id, type) VALUES ($1, $2) ON CONFLICT DO NOTHING", event.id, event.type)
    if inserted == "INSERT 0 1":
        await queue.enqueue("handle_stripe_event", event.id)        # worker re-fetches the object
    return {"received": True}

Common pitfalls

  • Doing heavy work before returning 2xx (timeouts → retries → duplicates).
  • Assuming event order.
  • No backfill after webhook outages.

Key points

  • Verify, dedupe, ack, process async
  • Re-fetch for current state
  • Backfill/reconcile periodically

Further reading: Stripe: Webhooks

Practice questions

Why re-fetch the payment object when processing a webhook?

Answer

Events can arrive late or out of order; fetching current state from the PSP ensures you act on the latest truth.

How do you catch payment events missed during a webhook outage?

Answer

Run a periodic job that lists recent events or objects from the PSP API and applies any not yet processed.

Open in the app to mark lessons complete, take notes and track your progress.