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 = falseThe 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.