Nic brief · ~1–2h · no n8n

Cart-Abandon Recovery → ActiveCampaign

From Aga (via Claude) · 22 Jul 2026 · Reuses your Stripe/analytics work — no webhook change, no n8n.

TL;DR — When someone reaches the subscription checkout, enters their email, and leaves without paying, we want to tag them in ActiveCampaign so a recovery email sequence fires. The email side is fully built and waiting on Aga's end. This brief is the one missing piece: getting the "they abandoned" signal (and which checkout they were on) out of Stripe and into AC. It's two small pieces of code, no webhook.

1 · Why this matters

Abandoned checkouts are the highest-intent non-buyers in the whole funnel — the card was out, they just didn't finish. Right now we say nothing to them. Two AC automations (Part 1 & Part 2 – Abandoned Cart Reminder) have been live since October and have fired 0 times, ever — because nothing tells AC the abandon happened. Fixing it is worth roughly $200–350/mo, essentially free once wired.

2 · The finding (this changed the approach — for the better)

I checked the Stripe side first. Our live checkouts run on PaymentIntents / Payment Element, not hosted Stripe Checkout Sessions (last Checkout Session created 30 Jun; fresh PaymentIntents every day). So Stripe's native checkout.session.expired event will never fire for us — there's no clean "cart expired" webhook to hang off.

Good news: the replacement is smaller than a webhook — no changes to your existing billing or analytics webhooks at all.

3 · The plan — two small pieces

1

Capture on the checkout (front-end JS)

When the user fills in the email field, write two things into the PaymentIntent metadata:

  • buyer_email — this key exists today but stays empty until payment succeeds, so every abandoned/stuck PaymentIntent currently has no email on it. Filling it on blur is what lets us reach them.
  • the offer + plan they're on — e.g. offer=50off, plan=12month (or the full resume URL). This is what lets the recovery email send them back to the exact checkout they were on, not a generic page.
2

Hourly reconcile (one small standalone script — no webhook)

Every hour (cron), list PaymentIntents stuck in requires_payment_method / requires_confirmation, created >2h ago, that now carry a buyer_email and never succeeded. For each one:

  • AC: upsert the contact, then apply the Subscription Cart Abandoned tag (API tag id 63598)
  • AC: set a custom field Abandoned Checkout URL = the resume link rebuilt from the metadata — the recovery email links to this so each person returns to their own offer
  • Skip anyone already a customer ([TMA] Customer tag, or on .TMA [CURRENT CUSTOMERS] / [Lifetime CUSTOMERS])

The AC API calls

Base https://bwta.api-us1.com/api/3/, header Api-Token: <AC_API_KEY> (Aga has the key).

# 1) upsert the contact
POST /contact/sync
{"contact": {"email": "<buyer_email>"}}            → returns contact.id

# 2) apply the Subscription Cart Abandoned tag (id 63598)
POST /contactTags
{"contactTag": {"contact": "<contact.id>", "tag": "63598"}}

# 3) stamp their exact checkout URL into the custom field (field id 56)
POST /fieldValues
{"fieldValue": {"contact": "<contact.id>",
                "field": "56",
                "value": "https://app.themovementathlete.com/payments/checkouts-v3/{offer}/{plan}/"}}

✅ The custom field already existsAbandoned Checkout URL, field id 56, personalisation tag %ABANDONED_CHECKOUT_URL% (that's what the email CTA uses). Nothing to create — just write to field id 56.

4 · How the dynamic link works (so you know what the field is for)

Your script drops each person's exact checkout URL into their AC contact record. The email's CTA button is set to that field's personalisation tag (like %FIRSTNAME%, but for the URL) — so AC swaps in their link when it sends. Same email, right offer for every recipient. Nothing dynamic needs to happen on your side beyond writing the field.

5 · Test / done-when

Aga's side is already done — both automations (Part 1 + Part 2, with split tests, customer exclusions, and buy-and-exit goals) are built and wired. The whole thing is waiting on this signal. Once your test contact lands with the tag, she just confirms it fired and switches it live.

6 · Where to hook it + cadence

No rush on any of it — whenever you're back in that area. Full internal record lives in docs/company/ANALYTICS/TRACKING-SYSTEM/. Thanks, Nic 🙌