Guides / Payments

How to Track Refunds and Chargebacks

3 min read·Payments

Why you need this

High chargeback rates (above 1%) can get your Stripe account terminated. Early detection gives you time to respond and de-escalate. Refunds are less critical but still important for revenue tracking.

Webhook handler for refunds and disputes

ts
// app/api/webhooks/stripe/route.ts (add to existing handler)
case "charge.refunded": {
  const charge = event.data.object as Stripe.Charge;
  const amount = charge.amount_refunded / 100;
  const currency = charge.currency.toUpperCase();

  await logit.now("refunds", {
    event: "Refund issued",
    description: `$${amount.toFixed(2)} ${currency}${charge.billing_details?.email ?? "unknown"}`,
    icon: "↩️",
    notify: true,
    tags: { reason: charge.refunds?.data[0]?.reason ?? "unknown" },
    metadata: {
      chargeId: charge.id,
      amount,
      currency,
      email: charge.billing_details?.email,
    },
  });
  break;
}

case "charge.dispute.created": {
  const dispute = event.data.object as Stripe.Dispute;
  const amount = dispute.amount / 100;

  await logit.now("chargebacks", {
    event: "Chargeback filed!",
    description: `$${amount.toFixed(2)} disputed — reason: ${dispute.reason}`,
    icon: "🚨",
    notify: true,
    tags: { reason: dispute.reason, status: dispute.status },
    metadata: {
      disputeId: dispute.id,
      chargeId: dispute.charge as string,
      amount,
      reason: dispute.reason,
      dueBy: new Date(dispute.evidence_details?.due_by ?? 0).toISOString(),
    },
  });
  break;
}

Add these events to your Stripe webhook

  • charge.refunded
  • charge.dispute.created
  • charge.dispute.updated
  • charge.dispute.closed

Tip: Set notify: true on charge.dispute.created — disputes have a short window to respond (typically 7-21 days) and every hour counts.

Try LogIt free

7-day trial. No credit card required.

Start free