SilentChat

Referral Program — Stripe Integration

Last updated: September 9, 2026

Reward Paths

Three reward types, three different Stripe mechanisms.

credit — Customer Balance Transaction

A customer.balance_transaction with a negative amount is booked to the referrer Customer. Stripe automatically applies the credit to the next invoice (negative position "Referral Credit").

  • Referral duration: as long as the Stripe Customer exists.
  • Multi-Currency: Stripe automatically converts to the Customer currency; we log the conversion rate in the referral notes.
  • Treated as a pre-tax discount when Stripe Tax is active — the sales tax is reduced proportionally.

coupon — Subscription Discount

The configured Stripe Coupon is applied to the active subscription of the referrer via subscription.update (field discounts). It affects the next invoice.

  • Important: Stripe does not stack two coupons; a new add replaces a previous coupon on the same subscription.
  • Coupon must exist: when activating a new reward setup, test manually once (qualify advertisement on test tenant + grant).

trial_extension — Subscription Trial End Update

subscription.update with a new trial_end (old value + configured days). proration_behavior=none to prevent mid-cycle calculation from being triggered.

  • Only effective if subscription.status == trialing; converted subscriptions are no-op (grant routine should then fall back to credit — currently, the lifecycle status is set to rewarded with reward_days snapshot, but no Stripe mutation).

Idempotency

Each Stripe call carries a deterministic idempotency key:

  • Grant: referral_grant_<referral_id>
  • Clawback: referral_clawback_<referral_id>

This means: a second cron tick or a manual click on "Pay out reward" produces no duplicate booking — Stripe recognizes the key and returns the same transaction.

Clawback

The customer.subscription.deleted webhook (or charge.refunded / charge.dispute.created) calls ClawbackOnChurn(tenantID). For each rewarded referral in the clawback window:

  • Credit: reverse balance_transaction with a positive amount.
  • Coupon / Trial Extension: not retro-reversible — the referral is only marked as canceled locally + audited.

Stripe Outage

If the grant Stripe call fails:

  1. The referral remains on status Qualified (not Rewarded).
  2. The Notes column gets a timestamp + error reason.
  3. A referral.grant_failed audit event is written.
  4. The next cron tick tries again — the idempotency key guarantees no double booking.

If a single referrer permanently does not have a Stripe Customer (e.g., manual subscription without Stripe setup): the grant fails every time. Solution: either manually create the Stripe Customer, or manually cancel the referral.

Coupon Conflicts

  • Stripe Coupon has max_redemptions=1 and has already been redeemed elsewhere → grant fails, admin alert.
  • Stripe Coupon has been deleted → grant fails, admin alert.
  • Stripe Coupon has expired (redeem_by timestamp exceeded) → grant fails.

In all three cases: the referral remains Qualified; the admin fixes the coupon and triggers the next cron tick (or manually clicks "Pay out reward" on the detail page).

Referral Program — Stripe Integration — Help Center — SilentChat | SilentChat