subscription.payment_succeeded

Fires on every successful charge against a subscription: the first charge after trial-end, every renewal, every dunning retry that lands. The event is the source-of-truth for "money came in for this subscription"; subscribe if you book revenue per-charge rather than per-cycle.

When it fires

Three code paths:

  1. First charge — trial ended or there was no trial. Fires alongside (or just after) subscription.created.
  2. Renewal — cycle rolled over and the renewal charge succeeded. Fires alongside subscription.renewed.
  3. Retry success — a past_due subscription's retry landed. Fires alongside subscription.renewed.

Payload

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

Slim — fetch the subscription for amount, currency, customer, and current cycle. The reason for the minimal payload is that the renewal cron emits this before billing-detail propagation, so the source-of-truth lookup is GET /v1/payment/subscriptions/:id.

Handler examples

// Node
if (event.type === 'subscription.payment_succeeded') {
  const sub = await client.subscriptions.retrieve(event.data.subscriptionId);
  await revenue.book({
    subscriptionId: sub.id,
    amount: sub.planAmount,
    currency: sub.planCurrency,
    period: { start: sub.currentPeriodStart, end: sub.currentPeriodEnd },
  });
}
# Python
if event["type"] == "subscription.payment_succeeded":
    sub = client.subscriptions.retrieve(event["data"]["subscriptionId"])
    revenue.book(subscription_id=sub["id"], amount=sub["planAmount"], currency=sub["planCurrency"])
// Go
if event.Type == "subscription.payment_succeeded" {
    var d struct{ SubscriptionID string `json:"subscriptionId"` }
    _ = json.Unmarshal(event.Data, &d)
    sub, _ := client.Subscriptions.Retrieve(ctx, d.SubscriptionID)
    revenue.Book(ctx, sub)
}

What to do

  • Book revenue (one row per event, keyed on event.id so retries don't double-count).
  • Restore entitlement if the subscription was previously in past_due — this charge brought it back.
  • Trigger your own "thanks for renewing" mail if you don't trust Storlaunch's default receipt.

Common pitfalls

  • Double-counting with subscription.renewed. Both fire on every successful renewal. Pick one for revenue booking and dedupe the other.
  • Assuming first-charge means trial-converted. The first payment_succeeded can fire because trial ended OR because there was no trial. Read the previous subscription state from your DB if the distinction matters.
  • Not deduping retries. The dunning loop fires payment_failed then payment_succeeded on retry — both have unique event.ids but reference the same subscription. Be ready for sequences.

Related events

Next