# Audit failed Stripe payments in DynamoDB with a Slack triage note

> When a Stripe charge fails, save the failure to your DynamoDB audit table with duplicate protection and post a Slack triage line that says how worried to be.

- Workflow type: agent
- Services: Amazon DynamoDB, Stripe, Slack Bot
- Categories: Finance, Operations
- Published: 2026-07-17

## What it does

- Fires the moment Stripe reports a failed charge.
- Pulls the customer's name, email, and last 90 days of Stripe charges to see if this failure is a one-off or a pattern.
- Writes a durable row to your DynamoDB payment_failures table with duplicate protection, so retried webhooks do not double-log.
- Posts a one-line triage to your finance Slack channel that classifies the failure and links to the Stripe dashboard entry.

## What you'll need

- A Stripe account with API access, and a charge.failed webhook pointed at this workflow.
- An AWS account with a DynamoDB table named payment_failures (customer_id as the partition key, charge_id as the sort key).
- A Slack workspace and the internal channel you want triage notes posted to.

## Prompt

When Stripe fires a charge.failed webhook, log the failure to a DynamoDB audit table and post a triage line to the finance Slack channel that classifies how worried we should be.

Trigger: Stripe webhook, event type charge.failed. The event payload includes the failed charge id, customer id, amount, and failure reason.

Steps:

1. Read the charge id, customer id, amount, and failure reason from the charge.failed event payload.
2. Use Stripe Retrieve Customer with that customer id to pull the customer's name and email.
3. Use Stripe List Charges filtered by that customer id and a created timestamp in the last 90 days, so we can see prior successes and failures in that window. Count the failures (status = failed) for use in the classification and the audit row.
4. Use Amazon DynamoDB Query against the payment_failures table with a KeyConditionExpression on the customer_id partition key, to fetch any prior failure rows already on file for this customer.
5. Use Amazon DynamoDB Put Item to write a new row to payment_failures with customer_id as the partition key, charge_id as the sort key, and the following attributes: amount, failure_reason, customer_id, timestamp (event created), and prior_90d_failures (the count from step 3). Set ConditionExpression to attribute_not_exists(charge_id) so a retried webhook cannot double-write. If DynamoDB returns ConditionalCheckFailedException, treat it as an already-logged duplicate: stop the workflow cleanly and do NOT post to Slack again.
6. Use Slack Bot Send a Message to post a single-line triage note to the finance channel. Include the customer name, the amount, the failure reason, and a link to the Stripe dashboard entry at https://dashboard.stripe.com/payments/{charge_id}. Prefix the note with exactly one of these three classifications:
   
   - "first time, likely a card issue" — the customer has zero prior failures in the last 90 days AND is not over the high-value threshold.
   - "recurring, dunning candidate" — the customer has one or more prior 90-day failures AND is not over the high-value threshold.
   - "high-value customer, human review" — the customer's lifetime revenue exceeds the configurable threshold, regardless of prior failure count.

Computing lifetime revenue: sum the amount of all succeeded charges returned by Stripe List Charges for the customer. If you already carry lifetime revenue elsewhere, use that instead.

Rules:

- Never DM the customer directly. This workflow is internal only. No Stripe emails, no SMS, no customer-facing messages.
- Only escalate to "high-value customer, human review" when lifetime revenue exceeds the configured threshold.
- If Put Item's ConditionExpression fails (ConditionalCheckFailedException), stop the workflow. Do not re-post to Slack. This is the expected idempotency path for retried Stripe webhooks.
- The DynamoDB table must exist ahead of time with customer_id as the partition key and charge_id as the sort key.

Configurables:

- financeChannelId — the Slack channel id the triage note is posted to.
- highValueRevenueThresholdUsd — lifetime revenue above which a customer is escalated to human review. Default: 10000.
- lookbackDays — window used to count prior failures and successes. Default: 90.
- tableName — DynamoDB table name. Default: payment_failures.

## How to customize

- The lifetime revenue threshold that flips a customer into human-review territory.
- Which Slack channel gets the triage note (finance, revenue ops, on-call).
- The 90-day lookback window used to decide whether a failure is a first-timer or a repeat.
- The DynamoDB table name if you already use a different one for audit rows.

## FAQ

### Does this message the customer directly?

No. This workflow is internal only. It writes to your audit table and posts to your Slack channel. It never emails, texts, or DMs the customer.

### What happens if Stripe retries the same webhook?

The database write is protected against duplicates on the charge id, so a retried webhook cannot create a second row or fire a second Slack alert. Retries stop cleanly.

### Do I need to create the DynamoDB table first?

Yes. Create a table (default name: payment_failures) with customer_id as the partition key and charge_id as the sort key before you turn the workflow on.

### What counts as high-value for the human-review escalation?

Whatever number you configure. The default is a lifetime revenue threshold you set once, and any customer over that number gets flagged for a human to review instead of routed to normal dunning.

### Can I use Microsoft Teams or Discord instead of Slack?

Yes. Swap the Slack step for whichever team chat you use. The rest of the workflow (Stripe lookup, DynamoDB audit row, classification logic) stays identical.

Use this prompt in General Input: https://www.generalinput.com/prompts/audit-failed-stripe-payments-in-dynamodb-with-a-slack-triage-note