Bill
Monitoring

Monitor Automatic Payments

Track automatic payment activity and handle failures


Monitor automatic payment activity to track collection success rates, identify failed payments, and implement recovery strategies. Query automatic payment transactions, watch for failed attempts, set up webhook notifications for real-time monitoring, and build processes to handle payment failures gracefully. Effective monitoring improves collection rates and customer experience.

Prerequisites

Before monitoring automatic payments, ensure you understand:


Querying Automatic Payments


Find and analyze automatic payment transactions.

import payload
pl = payload.Session('secret_key_3bW9...', api_version='v2')
from datetime import datetime, timedelta
 
# Query automatic payment transactions
 
# Find all automatic payments
all_autopay = pl.Transaction.filter_by(
    q='type == "payment" && trigger == "automatic"',
    order_by='desc(created_at)'
).all()
 
print(f'Total automatic payments: {len(all_autopay)}')
 
# Find recent automatic payments (last 7 days)
seven_days_ago = datetime.now() - timedelta(days=7)
date_filter = seven_days_ago.isoformat()
 
recent_autopay = pl.Transaction.filter_by(
    q=f'type == "payment" && trigger == "automatic" && created_at >= "{date_filter}"',
    order_by='desc(created_at)'
).all()
 
print(f'\nAutomatic payments in last 7 days: {len(recent_autopay)}')
 
# Calculate success rate
successful = len([t for t in recent_autopay if t.status == 'completed'])
failed = len([t for t in recent_autopay if t.status == 'failed'])
success_rate = (successful / len(recent_autopay) * 100) if recent_autopay else 0
 
print(f'Successful: {successful}')
print(f'Failed: {failed}')
print(f'Success rate: {success_rate:.1f}%')

Filtering automatic payments:

  • Use trigger == "automatic" to find autopay transactions
  • Combine with type == "payment" to filter payment types
  • Use created_at date filters to find recent payments
  • Check status field for payment success or failure

Transaction statuses:

  • completed - Payment successful
  • failed - Payment failed (will retry automatically)
  • pending - Payment being processed
  • cancelled - Payment was cancelled

Monitor specific customer

# Find all automatic payments for a customer
customer_payments = pl.Transaction.filter_by(
    q='type == "payment" && trigger == "automatic" && sender.account_id == "acct_customer123"',
    order_by="desc(created_at)"
).all()
 
print(f"Customer automatic payments: {len(customer_payments)}")
 
total_paid = 0
successful_count = 0
failed_count = 0
 
for payment in customer_payments:
    if payment.status == "completed":
        total_paid += payment.amount
        successful_count += 1
    elif payment.status == "failed":
        failed_count += 1
 
print(f"Successful: {successful_count}")
print(f"Failed: {failed_count}")
print(f"Total collected: ${total_paid}")

Daily automatic payment summary

# Get today's automatic payment activity
from datetime import date
 
today = date.today().isoformat()
 
todays_payments = pl.Transaction.filter_by(
    q=f'type == "payment" && trigger == "automatic" && created_at >= "{today}T00:00:00Z"',
    order_by="desc(created_at)"
).all()
 
successful = [p for p in todays_payments if p.status == "completed"]
failed = [p for p in todays_payments if p.status == "failed"]
total_amount = sum(p.amount for p in successful)
 
print("Today's Automatic Payments:")
print(f"  Attempts: {len(todays_payments)}")
print(f"  Successful: {len(successful)}")
print(f"  Failed: {len(failed)}")
print(f"  Amount Collected: ${total_amount}")
if todays_payments:
    print(f"  Success Rate: {(len(successful) / len(todays_payments)) * 100:.1f}%")

Monitoring Failed Payments


Identify and track invoices with failed automatic payment attempts.

from datetime import datetime, timedelta
 
import payload
 
pl = payload.Session("secret_key_3bW9...", api_version="v2")
 
# Handle failed automatic payments
 
 
# Find invoices with recent failed automatic payments
def find_failed_autopay_invoices():
    five_days_ago = datetime.now() - timedelta(days=5)
    date_filter = five_days_ago.date().isoformat()
 
    invoices = pl.Invoice.filter_by(
        q=f'status == "unpaid" && autopay_settings.allowed == true && due_date >= "{date_filter}"',
        order_by="asc(due_date)",
    ).all()
 
    failed_invoices = [
        invoice for invoice in invoices if any(p.amount == 0 for p in invoice.payments)
    ]
 
    return failed_invoices
 
 
# Send payment failure notification
def notify_failed_payment(invoice):
    print(f"Failed payment for invoice: {invoice.id}")
    print(f"Customer: {invoice.payer['account_id']}")
    print(f"Amount due: ${invoice.totals['balance_due']}")
    print(f"Due date: {invoice.due_date}")
 
    # Notify customer to update payment method
    # send_email({ ... })
 
 
# Main execution
failed_invoices = find_failed_autopay_invoices()
 
print(f"Found {len(failed_invoices)} invoices with failed autopay")
 
for invoice in failed_invoices:
    notify_failed_payment(invoice)

Failed automatic payments create zero-amount payment allocations on invoices for tracking.

Identifying failed attempts:

  • Zero-amount payment allocations indicate failed attempts
  • Invoice remains in unpaid or partially_paid status
  • Automatic retry continues for up to 5 days
  • After 5 days, invoice requires manual intervention
# Check invoice payment history including failed attempts
invoice = pl.Invoice.get("inv_abc123")
 
print("Payment attempts:", len(invoice.payments))
print("Current status:", invoice.status)
print("Balance due:", invoice.totals["balance_due"])
 
for index, payment in enumerate(invoice.payments):
    print(f"\nAttempt {index + 1}:")
    print("  Amount:", payment.amount)
    print("  Date:", payment.created_at)
 
    if payment.amount == 0:
        print("  Status: Failed attempt - will retry")
    else:
        print("  Status: Successful payment")
 
# Check if invoice is in retry period
from datetime import date
 
due_date = date.fromisoformat(invoice.due_date)
today = date.today()
days_since_due = (today - due_date).days
 
if 0 <= days_since_due <= 5 and invoice.status == "unpaid":
    print(f"\nIn retry period - day {days_since_due} of 5")

Tracking payment attempts:

  • Each payment attempt creates a payment allocation on the invoice
  • Failed attempts are marked with zero amount
  • Successful payments show the actual amount charged
  • Check retry period status to determine if invoice will auto-retry
# Find invoices with recent failed automatic payments
from datetime import date, timedelta
 
five_days_ago = (date.today() - timedelta(days=5)).isoformat()
 
invoices = pl.Invoice.filter_by(
    q=f'status == "unpaid" && autopay_settings.allowed == true && due_date >= "{five_days_ago}"',
    order_by="asc(due_date)"
).all()
 
failed_invoices = [
    inv for inv in invoices
    if any(p.amount == 0 for p in inv.payments)
]
 
print(f"Found {len(failed_invoices)} invoices with failed autopay")
 
for invoice in failed_invoices:
    print(f"Invoice {invoice.id}: ${invoice.totals['balance_due']} due")
    print(f"  Customer: {invoice.payer['account_id']}")
    print(f"  Due date: {invoice.due_date}")

Bulk monitoring approach:

  • Query all unpaid invoices with autopay enabled
  • Filter by recent due dates (within retry window)
  • Check for zero-amount payment allocations
  • Identify customers requiring notification

Webhook Monitoring


Receive real-time notifications for automatic payment events.

Setup webhook for automatic payments

# Webhook handler for automatic payments
from flask import Flask, request
 
app = Flask(__name__)
 
@app.route("/webhooks/payload", methods=["POST"])
def handle_webhook():
    event = request.json
 
    if (
        event["type"] == "transaction.created"
        and event["data"]["trigger"] == "automatic"
        and event["data"]["type"] == "payment"
    ):
        transaction = event["data"]
 
        print("Automatic payment processed:")
        print(f"  Transaction: {transaction['id']}")
        print(f"  Amount: ${transaction['amount']}")
        print(f"  Status: {transaction['status']}")
        print(f"  Customer: {transaction['sender']['account_id']}")
 
        if transaction["status"] == "completed":
            handle_successful_autopay(transaction)
        elif transaction["status"] == "failed":
            handle_failed_autopay(transaction)
 
    return "", 200
 
 
def handle_successful_autopay(transaction):
    print("Payment successful - sending confirmation")
 
 
def handle_failed_autopay(transaction):
    print("Payment failed - notifying customer")

Invoice status webhooks

# Monitor invoice status changes from autopay
from flask import Flask, request
 
app = Flask(__name__)
 
@app.route("/webhooks/payload", methods=["POST"])
def handle_webhook():
    event = request.json
 
    if event["type"] == "invoice.updated":
        invoice = event["data"]
 
        if invoice["status"] == "paid" and invoice["autopay_settings"]["allowed"]:
            print(f"Invoice {invoice['id']} paid via autopay")
            print(f"Amount: ${invoice['totals']['total']}")
 
    return "", 200

Handling Failed Automatic Payments


Implement strategies for recovering from failed autopay attempts.

Schema Reference


Fields relevant to monitoring automatic payments:

Transaction Trigger

type
enum[string]Immutable
The type of transaction being processed. This determines the direction and nature of funds movement, such as payment collection, refund issuance, credit disbursement, or account funding operations.
Values: payment, deposit, withdraw, refund, payout
status
object
The status information for this transaction, including the current state, a detailed status code, and a human-readable message explaining the result.
code
enum[string]
A machine-readable code detailing the specific reason or result for the transaction status. This provides granular information about success, failure reasons, or other status conditions.
Values: approved, card_expired, duplicate_attempt, exceeded_limit, general_decline, insufficient_bal, invalid_card_code, invalid_card_number, invalid_zip, invalid_address, invalid_account_number, suspicious_activity, too_many_attempts, processing_issue, issue_reading_card, not_supported, general_reject, general_adjustment, payment_stopped
Read-only permissions: "Undocumented"
message
stringRead-only
A human-readable message describing the transaction status and code. This text is suitable for display to users and provides context about the transaction outcome.
value
enum[string]
The current processing status of the transaction, indicating its lifecycle state such as processing, processed, authorized, declined, voided, or rejected.
Values: processing, authorized, processed, declined, rejected, voided, adjusted
created_at
string (date-time)Read-only
Timestamp when the resource was created. Automatically set by the system and immutable.

Next Steps

Improve automatic payment operations and analytics


Configure Autopay

Set up automatic payments with Enable Autopay to configure default payment methods and collection rules, stop automatic payments when needed with Disable Autopay for customer requests or policy changes, and understand payment processing with Autopay Overview for eligibility and retry logic.

Handle Manual Payments

Process manual invoice payments with Payment API for programmatic payment processing, send payment links to customers with Payment Requests via email or SMS, and record offline payments with External Payments for cash, check, and wire transfers.

Analyze Performance

Set up real-time monitoring with Webhook Events to receive notifications for automatic payment events, understand payment processing with Transaction Status for tracking payment lifecycle, and build custom reports with Reporting Overview for revenue and collection analytics.


Related articles