Prompt

I'm building a webhook listener for Stripe and GitHub — how should I structure it?

Technology / API Platforms1 observationLast seen Oct 5, 2026

Latest observation

Oct 5, 2026OpenAI APIWeb search: off

A good webhook listener structure is:

  1. One public endpoint per provider

    • e.g. /webhooks/stripe and /webhooks/github
    • Keep them separate because they have different auth, signing, payloads, and retry behavior.
  2. Verify authenticity before doing anything

    • Stripe: verify the Stripe-Signature header with your endpoint secret.
    • GitHub: verify the X-Hub-Signature-256 HMAC with your webhook secret.
    • Do this on the raw request body before parsing/mutating it.
  3. Parse minimally, route by event type

    • Read the event type first:
      • Stripe: event.type
      • GitHub: X-GitHub-Event header
    • Dispatch to handlers like:
      • handleStripeInvoicePaid
      • handleGitHubPullRequestOpened
  4. Acknowledge fast

    • Return 2xx as soon as the event is validated and queued/recorded.
    • Don’t do long DB work or API calls inline if you can avoid it.
    • Webhook providers retry on timeouts/non-2xx.
  5. Make processing idempotent

    • Store a unique event ID and ignore duplicates.
    • Stripe includes an event id.
    • GitHub delivery has an X-GitHub-Delivery UUID.
    • Assume retries and duplicate deliveries will happen.
  6. Use a queue/background worker

    • Webhook endpoint:
      • validate
      • persist event
      • enqueue job
      • return 200
    • Worker:
      • performs business logic
      • retries safely
      • logs failures
  7. Separate “ingestion” from “business logic”

    • Keep the HTTP layer thin.
    • Put provider-specific normalization in adapter code.
    • Put app logic in service classes/functions.
  8. Log enough for debugging

    • provider, delivery ID, event type, signature result, processing result
    • avoid logging secrets or full sensitive payloads if not necessary

Suggested structure

webhooks/
  stripe/
    controller
    verifier
    parser
    handlers/
  github/
    controller
    verifier
    parser
    handlers/
  common/
    event_store
    queue
    idempotency
    logging

Or in a simpler code organization:

src/
  routes/
    stripeWebhook.ts
    githubWebhook.ts
  services/
    stripe/
      verify.ts
      handleEvent.ts
    github/
      verify.ts
      handleEvent.ts
  jobs/
    processWebhook.ts
  db/
    webhookEvents.ts

Processing flow

  1. Receive request
  2. Capture raw body
  3. Verify signature
  4. Extract provider event ID + type
  5. Check idempotency store
  6. Persist event record
  7. Enqueue worker job
  8. Return 200 OK
  9. Worker processes event
  10. Update status / retry on failure

Important provider-specific notes

Stripe

  • Must use the raw body exactly as received for signature verification.
  • Event types can be many; only handle the ones you need.
  • Stripe retries for a long time, so idempotency is critical.
  • Good practice: use Stripe object IDs too, not just event IDs, when updating domain objects.

GitHub

  • Verify with X-Hub-Signature-256.
  • Use X-GitHub-Event to route.
  • GitHub may send ping events for setup health checks.
  • Delivery IDs are useful for deduplication.

Minimal pseudocode

POST /webhooks/:provider
  raw = readRawBody(request)

  if provider == "stripe":
    verifyStripeSignature(raw, headers)
    event = parseStripeEvent(raw)
    eventId = event.id
    eventType = event.type

  if provider == "github":
    verifyGitHubSignature(raw, headers)
    eventType = header["X-GitHub-Event"]
    eventId = header["X-GitHub-Delivery"]
    event = parseJson(raw)

  if alreadyProcessed(eventId):
    return 200

  storeEvent(eventId, provider, eventType, raw)
  enqueue("processWebhook", {provider, eventId})

  return 200

Common pitfalls

  • Parsing JSON before verifying signatures
  • Doing slow work before responding
  • Not handling duplicates
  • Not storing the raw payload
  • Using a single generic handler for both providers without normalization
  • Logging secrets or full PII unnecessarily

If you want, I can also give you:

  • a Node/Express example,
  • a Python/FastAPI example,
  • or a production-ready architecture with queue + DB schema.

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as Circuit. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.