1. Rowfire
  2. Use cases

Slack and Zendesk alerts when a card is declined

Tell #billing and open one Zendesk ticket per account when a charge fails in PostgreSQL, without a ticket for every retry. Trigger SQL and rules included.

  • PostgreSQL
  • Slack
  • Zendesk
  • Billing
  • once per account per week

The trigger

One read-only SELECT against PostgreSQL. Rowfire polls it for new rows, and the rule fires once per account per week.

SELECT a.id          AS account_id,
       a.name        AS account,
       a.owner_name,
       a.owner_email,
       p.name        AS plan,
       pa.invoice_no,
       pa.amount,
       pa.attempt,
       pa.decline_code,
       pa.attempted_at
FROM payment_attempts pa
JOIN accounts a ON a.id = pa.account_id
JOIN plans p    ON p.id = a.plan_id
WHERE pa.outcome = 'failed'
  AND a.is_internal = false

The clock is attempted_at and the key is account_id. Internal accounts are excluded in the query, so tests never reach the billing team.

Two rules on one trigger

rule fires at most action
billing_heads_up once per account per day Slack #billing
billing_ticket once per account per week Zendesk ticket, high priority

Templates pull any column from the row, for example:

Card declined for {{ account }}: {{ invoice_no }} ({{ amount }}, {{ decline_code }}).

What the backtest shows

On the demo’s 120 days of sample data, the trigger returns 211 declined charges. billing_ticket turns them into 105 tickets: the other 106 were retries that would have been duplicates. billing_heads_up posts 126 Slack messages over the same period.

Run both in shadow first: every request is rendered and recorded, nothing is sent. Promote each rule when its numbers look right.

Questions

Why key it by account and not by payment attempt?

Three declines on one invoice are one billing problem. Keying by account means the rule's cadence, not the number of retries, decides how often billing hears about it.

Can billing get Slack daily but tickets weekly?

Yes. Both rules subscribe to the same trigger, which is polled once and fanned out, each with its own cadence.