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.
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.
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.
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.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.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:
[TMA] Customer tag, or on .TMA [CURRENT CUSTOMERS] / [Lifetime CUSTOMERS])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 exists — Abandoned 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.
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.
buyer_email + offer/plan).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.
buyer_email + offer + plan onto the PaymentIntent metadata (via the backend, since metadata writes are server-side). Set it at PI creation or update the existing PI, whichever is cleaner in your code — the only requirement is that a stuck PI ends up carrying those three values.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 🙌