Skip to main content

benecaster_donation_decline_alert_thresholds

Filter Free

Filters the thresholds at which Benecaster warns a podcaster in the notification bell that a show’s donation declines have spiked. Core counts successes and failures over a rolling 24 hours per show and alerts at 10 or more attempts with 40% or more declining.

Both halves matter, and min_attempts is the one people delete. One genuinely expired card on a quiet day is a 100% decline rate. An alert that fires on that is an alert the podcaster learns to ignore, which costs more than it saves — core clamps the floor to a minimum of 1 for exactly that reason.

The counters are fed by the show’s Stripe payment_intent.payment_failed webhook event alone. A show subscribed only to payment_intent.succeeded produces no declines, no counters and no alert — silence there is absence of data, not health.

This does not detect card testing; it raises the cost and reports the shape. Stripe Radar’s card-testing rules recognise an attack, and they live on the podcaster’s own Stripe account. Any surface you build on this should say so rather than reading as the defence.

See a declined donation, and tune the decline-rate alert

Free Intermediate

The action is the only way to observe a declined donation. Nothing is written to benecaster_listener_support_donations on a failure, deliberately: that table is money received, and every reader of it — the admin donation list, the Donations dashboard panel, the Analytics Dashboard add-on — would count a failure row as a donation. There is no failure table and none is planned.

It fires only for Listener Support donations — the handler ignores any Stripe intent without a benecaster_show_id, so membership and buy-up failures on the same Stripe account do not reach it. And it fires only when the show’s Stripe webhook is configured, because the event is the signal. A show with no webhook produces no declines, no counters and no alert — silence there is absence of data, not health.

Core counts both outcomes over a rolling 24 hours per show and warns the podcaster in the notification bell at ≥10 attempts and ≥40% declines. Tune it per show with benecaster_donation_decline_alert_thresholds.

Both halves of the threshold matter, and min_attempts is the one people delete. One genuinely expired card on a quiet day is a 100% decline rate. An alert that fires on that is an alert the podcaster learns to ignore, which costs more than it saves — core clamps the floor to a minimum of 1 for exactly that reason.

None of this detects card testing; it raises the cost and reports the shape. The thing that recognises an attack is Stripe Radar’s card-testing rules, on the podcaster’s own Stripe account. Any surface you build on this action should say so rather than reading as the defence.

<?php
/**
 * @param string $decline_code Stripe's `decline_code` when present
 *                             (`stolen_card`, `insufficient_funds`, ...),
 *                             otherwise its generic `code`, else ''.
 */
add_action( 'benecaster_donation_payment_failed', function ( int $show_id, string $intent_id, string $decline_code ): void {
    if ( in_array( $decline_code, [ 'stolen_card', 'lost_card', 'pickup_card' ], true ) ) {
        error_log( "benecaster: hostile decline on show {$show_id} ({$decline_code})" );
    }
}, 10, 3 );

add_filter( 'benecaster_donation_decline_alert_thresholds', function ( array $thresholds, int $show_id ): array {
    // A high-traffic show can afford to alert sooner.
    return 42 === $show_id
        ? [ 'min_attempts' => 25, 'rate' => 0.25 ]
        : $thresholds;
}, 10, 2 );

View on GitHub →

Parameters

Name Type Default Description
$thresholds array Associative array with keys `min_attempts` (int, default 10) and `rate` (float 0-1, default 0.4).
$show_id int ID of the show

Returns: array

Example

add_filter( 'benecaster_donation_decline_alert_thresholds', function( $thresholds, $show_id ) {
    // A high-traffic show can afford to alert sooner.
    return 42 === (int) $show_id
        ? [ 'min_attempts' => 25, 'rate' => 0.25 ]
        : $thresholds;
}, 10, 2 );

Need this built rather than just documented? See our services →