subscription.past_due

Fires when a subscription transitions to past_due — the first time a charge attempt fails. After this, the dunning loop kicks in (default schedule: retry at T+1d, T+3d, T+7d) until a charge succeeds (returning the subscription to active) or all retries exhaust (moving it to unpaid and eventually canceled).

This event is your start-of-grace-window signal — it's where you'd decide to send a "your subscription needs attention" notification distinct from per-attempt failures.

When it fires

Single-shot per dunning episode. The subscription status transitions active → past_due exactly once per failure-cycle:

  1. Renewal charge fails.
  2. failedPaymentCount increments to 1.
  3. Status flips to past_due.
  4. This event fires.
  5. subscription.payment_failed also fires for the underlying attempt.

If the retry succeeds, status goes back to active and subscription.payment_succeeded + subscription.renewed fire. If the failure repeats, you get subscription.payment_failed per attempt but subscription.past_due is not re-fired (the subscription was already past-due).

Payload

{
  "id": "evt_01HX...",
  "type": "subscription.past_due",
  "createdAt": "2026-06-01T00:00:19Z",
  "accountId": "acc_01HX...",
  "data": {
    "subscriptionId": "sub_01HX..."
  }
}

Slim — fetch the subscription for current cycle, plan, customer.

Handler examples

// Node
if (event.type === 'subscription.past_due') {
  const sub = await client.subscriptions.retrieve(event.data.subscriptionId);
  await emails.sendDunningKickoff(sub.customerId, { graceUntil: addDays(sub.currentPeriodEnd, 7) });
  await access.markDegraded(sub.customerId);
}
# Python
if event["type"] == "subscription.past_due":
    sub = client.subscriptions.retrieve(event["data"]["subscriptionId"])
    emails.send_dunning_kickoff(sub["customerId"])
    access.mark_degraded(sub["customerId"])
// Go
if event.Type == "subscription.past_due" {
    var d struct{ SubscriptionID string `json:"subscriptionId"` }
    _ = json.Unmarshal(event.Data, &d)
    sub, _ := client.Subscriptions.Retrieve(ctx, d.SubscriptionID)
    notify.DunningKickoff(ctx, sub)
}

What to do

  • Notify the buyer that something's wrong with their payment method.
  • Mark their account "degraded" but keep access alive until your grace window expires — that's the whole point of dunning.
  • Surface the past-due state in your in-app UI so they can update their card without leaving your product.

Common pitfalls

  • Hard-revoking on past_due. This is the start of the grace window, not the end. Most past-due cycles recover within days.
  • Sending an email per failed attempt instead of one per dunning cycle. Subscribe to this event for the initial nudge; subscribe to subscription.canceled for the terminal state. The per-attempt payment_failed events are mainly for your CS team's escalation logic, not for user comms.
  • Forgetting to revert "degraded" state on recovery. When subscription.renewed or subscription.payment_succeeded arrives, flip the user back to full access.

Related events

Next