Skip to Content
FeaturesModulesPaymentsSignals & Tasks

Signals & Tasks

Two integration points for your app: signals to react to payment outcomes, and the RQ tasks that do the async work.


Signals

The engine emits two Django signals, both with sender=Payment and a payment keyword argument.

SignalFired when
payment_succeededA payment reaches succeeded (webhook- or reconciliation-driven).
payment_failedA payment reaches failed.
from django.dispatch import receiver from django_cfg.apps.payments.signals import payment_succeeded, payment_failed from django_cfg.apps.payments.models import Payment @receiver(payment_succeeded, sender=Payment) def on_paid(sender, payment, **kwargs): # payment.reference_kind / payment.reference_id tell you WHAT was paid for grant_access(payment.owner, payment.reference_id) @receiver(payment_failed, sender=Payment) def on_failed(sender, payment, **kwargs): notify_owner(payment.owner, payment)

payment_succeeded is emitted by the shared run_fulfillment path, so it fires whether the payment settled via webhook or reconciliation — exactly once, never twice.

Signal receivers vs. the fulfillment hook. A receiver exception propagates into the webhook’s record-and-retry path (so Stripe retries the delivery). A fulfillment_hook exception, by contrast, is logged and recorded on the event but not re-raised. Put must-not-fail side effects behind the hook and retry-desirable ones behind a signal — or make receivers defensive.

Signals vs. the fulfillment hook

Both fire on success. The fulfillment_hook is a single configured fn(payment) for “the” fulfillment action; signals are the fan-out for any number of independent listeners. They are complementary — use whichever fits, or both.


Background tasks (RQ)

The app uses django-rq (not dramatiq). Tasks are enqueued by dotted path onto the "default" queue.

process_webhook_event

django_cfg.apps.payments.tasks.process_webhook_event( provider_name, event_id, event_type, external_id, amount, currency, raw )

Enqueued by the webhook view on a valid signature. It reconstructs the normalized WebhookResult and calls the idempotent PaymentService.handle_webhook.

If RQ/Redis is unavailable, the webhook view runs handle_webhook inline instead — the async task is an optimization, not a hard dependency. See Webhooks.

reconcile_pending_payments

django_cfg.apps.payments.tasks.reconcile_pending_payments()

A scheduled job that runs ReconciliationService.reconcile_stuck(older_than_hours=1, limit=200) and logs a warning when anything was updated or errored. This is the automated form of the payments_reconcile command — schedule it to catch any webhook the app ever misses.

Schedule reconcile_pending_payments on a regular cadence (e.g. every few minutes) via your RQ scheduler. It is idempotent and cheap when there’s nothing stuck.

Last updated on