Skip to content
Gaurav TiwariDocs

Find Billing Problems with Billing Health

Updated November 7, 2026

A subscription store loses money quietly. A renewal fails and nobody notices, a webhook stops arriving, a scheduled job dies halfway, and you find out when a customer writes in to ask why their access stopped.

The Billing Health module puts everything that can stop money coming in on one screen: on-hold and overdue subscriptions, renewal charges that never finished, automatic renewals with no saved payment method, failed scheduled actions, payment gateway setup and webhook problems, and why customers cancelled lately. It fixes what Stackless can safely fix itself and logs every fix.

Turn On Billing Health

  1. Switch on the Billing Health module under Stackless → Dashboard. It’s off by default.
  2. Open Stackless → Billing health.

The screen checks everything again each time you open it, so there’s nothing to schedule. Only users with the manage_woocommerce capability can open it, which in WooCommerce means administrators and shop managers.

What the Screen Shows

Three counters sit at the top: Problems, Warnings and Notes. When all three are zero, the screen says “Nothing needs your attention.”

Below them, each source that found something gets its own section: Stackless itself, Subscriptions, then each payment gateway module you have on. A source with nothing to report doesn’t show. Inside a section, items are sorted with Problems first, then Warnings, then Notes. Each item has a short explanation, and many have an Open button that takes you to the subscriptions, orders, logs or settings involved.

Stackless Checks

  • Automatic billing is paused on this site (Problem): the site looks like a copy of the live store, so Stackless doesn’t charge saved payment methods, retry payments or email customers here. If this is the live store, confirm it in the notice at the top of the admin.
  • Stackless scheduled actions failed in the last 7 days (Warning): lists each failed job with a count. Open goes to WooCommerce’s scheduled actions, filtered to failed Stackless actions.
  • Leftover locks from interrupted requests (Note): locks expire on their own, so this is housekeeping. Clear old locks removes only locks older than an hour.

Subscription Checks

These show when the Subscriptions module is on:

CheckBadgeWhat to do
Subscriptions on hold after a failed or unpaid renewalWarningOpen lists them. The detail says how many won’t be retried because the bank declined the card for good or needs the customer to confirm. Those wait for the customer to pay or change the payment method.
Subscriptions past their renewal date with no renewal scheduledProblemClick Rebuild their schedules. Each one renews within a minute.
Renewal charges that started but never finishedProblemA request stopped mid-charge more than an hour ago. Check the payment at your provider before using Retry failed payment on the subscription, so the customer isn’t charged twice.
Renewals waiting more than a day for the payment providerWarningBank debits can take a few days. Otherwise check that the provider’s webhook reaches your site.
Subscriptions that renew automatically but have no saved payment methodWarningTheir renewals will fail and go to failed-payment retries. Ask the customers to add a payment method, or switch them to manual renewal.
Why customers cancelled in the last 30 daysNoteEach reason from the cancellation form with a count, most common first.

Overdue means an active or trial subscription that Stackless bills itself, due more than two hours ago and less than 60 days ago, with nothing unpaid and no renewal queued. Subscriptions your payment provider bills are left out, because the provider runs their schedule.

Payment Gateway Checks

Each payment module you have on adds its own section. Most checks only run for gateways you’ve turned on:

GatewayWhat it checks
StripeMissing API keys or keys from the other mode, a missing webhook signing secret, Managed Payments events missing from the webhook, test mode on the live store, and webhooks arriving for the other mode.
PayPalA missing client ID, secret or webhook ID, sandbox mode on the live store, and Fastlane turned on without the card fields.
SquareA missing application ID, access token or location, a missing webhook signature key, a location currency that differs from the store’s, and sandbox mode.
MollieNo API key for the selected mode, test mode, and charges pending for over two weeks.
RazorpayNo API keys or webhook secret, test mode, and debits pending for over five days.
Creem, Paystack, Flutterwave and Mercado PagoA missing key or a key for the other mode, webhooks that can’t be verified, no webhook received yet, and test mode.
Polar, Dodo Payments and PaddleWebhooks that failed or got stuck in the last 7 days.

Every gateway section also lists its recent webhook problems in a table with When, Event, Status and Note, so you can match them against the provider’s own delivery log.

Fix What Stackless Can Fix

Two items come with a fix button:

  • Rebuild their schedules, on overdue subscriptions: rebuilds each subscription’s renewal schedule from its dates and adds the note “Renewal schedule rebuilt from Billing health.” The renewal then runs the normal way, which charges the saved payment method or emails the invoice.
  • Clear old locks: deletes leftover locks older than an hour, and never one that a running request has just taken.

Fixes only re-run Stackless’s own steps. They don’t change anything at your payment provider. Everything else on the screen is a pointer: open the item and act on the subscription, order or gateway settings it names.

Who Can Run Fixes and the Fix Log

Fixes need the same manage_woocommerce capability as the screen, and they run from buttons on the screen, so nobody triggers one by following a link.

Every fix lands in the Fix log at the bottom of the screen, whether it worked or not:

  • When: the date and time.
  • Who: the username of the person who ran it.
  • Fix: the source and the fix, such as subscriptions · resync_overdue.
  • Result: the message, such as “Rebuilt the schedules of 3 subscriptions.” A failed fix is marked with ✕.

The log keeps the last 100 fixes and shows the latest 30.

Site Health

Billing Health adds a Stackless billing test to Tools → Site Health, with a Payments badge. It reads the same checks:

  • No Problems or Warnings: “Stackless billing has no problems.”
  • Warnings only: a recommended item, such as “Stackless billing: 0 problems, 2 warnings”.
  • Any Problem: a critical item with the same counts.

Notes don’t change the result. The test links to the screen with Open Billing health.

Limits

  • The screen shows what’s wrong right now. It doesn’t email you, and it keeps no history apart from the fix log.
  • There are two fixes: rebuilding overdue schedules and clearing old locks. Everything else needs you to act on the subscription, order or gateway.
  • Checks stop counting at a cap, such as 200 subscriptions or 50 orders per check, and list up to 20 numbers. On a large store, fix what’s listed and reload.
  • The overdue check looks back 60 days. Subscriptions overdue for longer don’t show.
  • Cancellation reasons come only from the customer cancellation form. They’re missing when Ask why they cancel is off, and when you cancel from the admin.
  • Subscription checks need the Subscriptions module, and each gateway’s checks need its module.

For Developers

Other code can add a section with stackless_billing_health_sources and run its own fixes with stackless_billing_health_fix. Use a lowercase source ID. Issues take severity (error, warning or info), title, detail, url, fix and fix_label. The fix filter gets the result so far, the source ID and the fix key. Return a message on success, a WP_Error on failure, or the value you got when the fix isn’t yours. Every call is written to the fix log.

add_filter('stackless_billing_health_sources', function ($sources) {
    $sources['my_crm'] = function () {
        $failed = (int) get_option('my_crm_sync_failures', 0);
        return [
            'label'  => 'CRM sync',
            'issues' => $failed ? [[
                'severity'  => 'warning',
                'title'     => sprintf('%d customer syncs failed.', $failed),
                'fix'       => 'reset',
                'fix_label' => 'Reset the counter',
            ]] : [],
        ];
    };
    return $sources;
});

add_filter('stackless_billing_health_fix', function ($result, $source, $fix) {
    if ($source !== 'my_crm' || $fix !== 'reset') {
        return $result;
    }
    update_option('my_crm_sync_failures', 0);
    return 'Counter reset.';
}, 10, 3);

For more hooks, see Hooks, Functions and Templates Reference.

Share

Was this helpful?

Still stuck? Open a support ticket