Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Persist inbound stripe webhooks #2972

Merged
merged 19 commits into from
Jan 3, 2025
Merged

fix: Persist inbound stripe webhooks #2972

merged 19 commits into from
Jan 3, 2025

Conversation

ancorcruz
Copy link
Contributor

@ancorcruz ancorcruz commented Dec 17, 2024

Context

Currently, when our application receives webhooks from third-party services (e.g., Stripe, GoCardless, Adyen), their payloads are immediately queued in Sidekiq for background processing. While this approach works in most cases, it introduces a critical reliability issue, especially in self-hosted environments where Sidekiq may lose jobs if workers are abruptly terminated or overloaded.

This unreliability has led to issues like pending transactions that are never resolved despite receiving valid webhooks. For example, a reported issue involved a wallet credit purchase where the payment settled on Stripe, the webhook was received, but the transaction remained pending indefinitely until manually resolved.

To address this, we aim to ensure that no webhook payload is lost and processing remains consistent, even under adverse conditions.

Description

This PR introduces a robust mechanism for handling inbound webhooks by persisting them in the database before processing. Key changes include:

Database Persistence:

  • Webhooks are now stored in a new inbound_webhooks table immediately upon receipt. This ensures all webhook payloads are safely persisted in a reliable system before processing.

Background Processing:

  • A new InboundWebhooks::CreateService is introduced to persist the webhook and enqueue it for processing.
  • A new InboundWebhooks::ProcessJob Sidekiq worker processes the persisted webhooks, invoking the InboundWebhooks::ProcessService
  • A new InboundWebhooks::ProcessService processes the persisted webhooks invoking the appropriate payment provider service.

Retry Mechanisms:

  • Failed webhooks are marked as such and can be retried manually or automatically via a clock job.
  • A cleanup job is introduced to remove processed webhook records older than 1 month.

Controller Update:

  • The WebhooksController actions (stripe, adyen, and gocardless) now use the InboundWebhooks::CreateService and respond with 200 OK once the webhook is persisted successfully.

Backward Compatibility:

  • No webhooks will be missed during the transition. The change is designed to integrate seamlessly into both cloud and self-hosted environments.

Key Changes

  • Added InboundWebhook ActiveRecord model to persist webhook payloads with fields such as source, event_type, payload, status.
  • Implemented InboundWebhooks::CreateService to handle webhook persistence and Sidekiq job enqueueing.
  • Added InboundWebhooks::ProcessJob to process persisted webhooks from Sidekiq queue.
  • Added InboundWebhooks::ProcessService to call the respective provider's handle incoming webhook service.
  • Updated PaymentProviders::Stripe::HandleIncomingWebhookService to process webhooks from the database instead of relying solely on direct Sidekiq queues.
  • Updated WebhooksController#stripe action to integrate with the new service and workflow.
  • Introduced a clock job for:
  1. Retrying pending webhooks older than 2 hours.
  2. Cleaning up processed webhooks older than 90 days.

Benefits

Reliability: Ensures no webhook is lost, even if Sidekiq workers fail.
Resilience: Provides a retry mechanism for failed webhook processing.
Transparency: Makes webhook processing traceable with statuses (pending, processing, processed, failed).

@ancorcruz ancorcruz added the 🐞 Bug Something isn't working label Dec 17, 2024
@ancorcruz ancorcruz self-assigned this Dec 17, 2024
@ancorcruz ancorcruz force-pushed the fix/persisted-webhooks branch 2 times, most recently from c083548 to d3e6956 Compare December 17, 2024 16:49
@ancorcruz ancorcruz force-pushed the fix/persisted-webhooks branch from 8fb4b83 to eb955bc Compare December 19, 2024 16:09
Copy link
Collaborator

@vincent-pochet vincent-pochet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

app/jobs/inbound_webhooks/process_job.rb Outdated Show resolved Hide resolved
app/services/inbound_webhooks/create_service.rb Outdated Show resolved Hide resolved
@ancorcruz ancorcruz force-pushed the fix/persisted-webhooks branch from e2becd3 to e9c1268 Compare January 2, 2025 14:45
@ancorcruz ancorcruz merged commit 8d43eac into main Jan 3, 2025
6 checks passed
@ancorcruz ancorcruz deleted the fix/persisted-webhooks branch January 3, 2025 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 Bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants