Why dunning needs to be configured carefully
Failed-payment recovery is one of the highest-ROI automations in the SaaS lifecycle — but it only works if the technical connections between your billing provider and GHL are set up correctly. A dunning workflow that fires on the wrong event, sends to the wrong contact, or fails to suppress when payment recovers creates a worse experience than no dunning at all.
This guide walks through every configuration step with enough detail to get it right on the first attempt.
Step 1 — Billing provider webhook configuration
Stripe
In your Stripe Dashboard, navigate to Developers → Webhooks → Add endpoint.
Endpoint URL: https://[your-ghl-subdomain].gohighlevel.com/webhook/dunning (exact URL provided in your snapshot onboarding).
Events to send:
invoice.payment_failed— triggers the dunning sequence start.invoice.payment_succeeded— triggers the success branch and suppresses remaining touches.customer.subscription.deleted— logs voluntary cancellation and excludes the contact from dunning (they cancelled intentionally — don’t chase a payment they don’t want to make).customer.subscription.updated— catches plan downgrades that may affect access levels.
Paddle
In your Paddle seller dashboard, navigate to Developer Tools → Notifications → New Destination.
Notification URL: your GHL webhook endpoint (provided in onboarding).
Events to send:
subscription.payment.failedsubscription.payment.succeededsubscription.cancelledsubscription.updated
Chargebee
In Chargebee, navigate to Settings → Configure Chargebee → Webhooks → Add Webhook.
Events to send:
payment_failedpayment_succeededsubscription_cancelledsubscription_changed
Step 2 — Field mapping verification
The SaaS Snapshot ships with pre-configured custom fields for dunning data. After setting up your billing webhook, trigger a test event from your billing provider’s test mode and verify that the following fields populate correctly on a test contact in GHL:
| Field | Expected value |
|---|---|
payment_failed_date | Date of failure (ISO 8601) |
payment_failure_reason | ”card_declined”, “insufficient_funds”, “expired_card”, or provider code |
subscription_status | ”past_due” or equivalent |
dunning_touch_count | Increments with each message sent (0 → 1 → 2 → 3 → 4) |
payment_recovery_date | Populated when recovery event fires (empty until then) |
If any field doesn’t populate, check the field mapping in GHL’s webhook configuration (Settings → Integrations → Webhooks) and verify the incoming payload key names match your billing provider’s documentation.
Step 3 — Dunning window configuration
The dunning window (default: 14 days from failed payment to access suspension) needs to match your business model:
Monthly subscriptions, low-touch self-serve: 14 days is appropriate. Long enough that customers have time to notice and fix the issue; short enough that you’re not extending credit indefinitely.
Annual contracts or enterprise accounts: Consider 21-30 days. A finance team may need a billing cycle to reissue a corporate card. Suspending an enterprise account at 14 days can damage the relationship.
Free + paid tiers: If customers can continue on a limited free tier when payment fails, the “suspension” event becomes a downgrade. Configure the workflow’s suspension branch to trigger a downgrade action rather than a full access block.
To change the dunning window: in GHL Workflows, find the Dunning - Day 14 - Final Warning workflow and update the delay between Touch 3 and Touch 4 (and the access suspension trigger) to match your window.
Step 4 — Template customization
Each of the four dunning email templates ships with placeholder text using [Product] and [billing portal URL] markers. Before going live, update every template:
Replace:
[Product]→ your product name.[billing portal URL]→ your direct billing portal URL (Stripe Customer Portal, Paddle billing page, or Chargebee self-serve portal). This must be a direct deep link that takes the customer immediately to the card update screen — not a login page they need to navigate from.
Verify:
- Each email renders correctly in both dark mode and light mode (test in Gmail, Outlook, and Apple Mail).
- The SMS messages are under 160 characters (or 320 for two concatenated segments).
- Your sender name on the email matches the name in your email signature template.
Step 5 — The success branch
The success branch is what prevents a customer who updates their card from continuing to receive dunning emails. Verify it does the following:
- Suppresses remaining touches: when the
invoice.payment_succeededorsubscription.payment.succeededevent fires, all scheduled dunning workflow steps for that contact are cancelled. - Removes the in-app banner: if you’ve implemented the in-app banner through a GHL custom field flag, the success branch must set that field back to
false. - Sends the recovery confirmation: a short, warm “all sorted” email confirming payment is resolved. Subject: “All sorted — your [Product] access continues.” Body: 3 sentences max. No CTAs.
- Logs the recovery: the
payment_recovery_datefield gets populated, and thedunning_touch_countat recovery is logged. This data powers your recovery rate reporting.
Step 6 — Test simulation
Before going live, run a complete test simulation:
- Create a test contact in GHL with a valid email address you control.
- In your billing provider’s test mode, trigger a
payment_failedevent for that contact’s subscription ID. - Verify Touch 1 fires (immediate email + in-app banner flag set).
- Fast-forward the workflow delays in GHL’s test mode to verify Touch 2 and Touch 3 fire in sequence.
- Trigger a
payment_succeededevent. Verify Touch 4 is cancelled and the recovery confirmation email fires. - Check that
payment_recovery_dateis populated on the contact.
If anything doesn’t fire correctly, check the workflow’s contact enrollment conditions — a common issue is the enrollment condition filtering on a contact tag that your test contact doesn’t have.
Measuring recovery rate
The SaaS Snapshot includes a dunning recovery dashboard in GHL. After your first 30 days live, you should be able to see:
- Failed payment volume: number of contacts that entered the dunning workflow.
- Recovery rate by touch: which touch (1, 2, 3, or 4) drove the card update.
- Unrecovered (churned): contacts who didn’t update and whose access was suspended.
- Monthly recovery revenue: estimated MRR recovered from the dunning sequence.
A well-configured dunning sequence should recover 30-50% of failed payments. If your rate is below 25% in the first 30 days, check: (a) is your billing portal URL a direct deep link or a generic homepage? (b) are your SMS messages actually delivering (check GHL’s SMS delivery logs)? (c) is Touch 2 firing correctly — the Day 3 SMS is often the highest-recovering single touch and missing it leaves significant recovery on the table.