Back to Playbooks

Refund Requests Without the Fire Drill: A Support Handoff Journey for Shopify Apps

Free

Explains an end-to-end Spreeflo journey for handling refunds and cancellations in Shopify and e‑commerce apps, turning billing events into automated support handoffs, internal alerts, and customer acknowledgements so small teams can manage churn without chaos.

Industry

Niche

Pattern

Loading sequence...

CartWizard’s founder used to start Mondays the same way: open Gmail, search “refund,” then manually copy each message into their support tool.

By the time a human finally replied, the merchant was often on their second angry email or a one‑star App Store review. No one was ignoring them on purpose; the team was just small, busy, and juggling product work with support.

The painful part? Every refund or cancellation looked the same operationally. An upset user. A billing event in Stripe. A frantic context‑gathering sprint before anyone could send a thoughtful reply.

You don’t need more people for that. You need a system.

The sequence at the top of this page is the whole journey, end to end. It listens for refund or cancellation events from your app, routes them to your support queue with all the context baked in, and sends the customer a clear acknowledgement without anyone lifting a finger.

In this article, we’ll walk that journey node by node, why it’s built the way it is, and how to adapt it to your own Shopify app or e‑commerce SaaS.

Why refunds and cancellations deserve their own automation

For an e‑commerce app, a refund or “cancel subscription” click is a critical moment:

  • It’s a churn event (or a near‑churn event).

  • The customer is often emotional.

  • App Store reviews and word‑of‑mouth are at stake.

Handled well, it can feel like white‑glove service: fast acknowledgement, clear expectations, and a smooth resolution. Handled badly, it becomes a public complaint about “unresponsive support” that scares off future installs.

The challenge for a 2–10 person team is that this moment shows up at random, across time zones, and always when you’re in the middle of shipping something else.

The journey you see above solves three specific problems:

  1. Every refund/cancellation event is captured as soon as it happens.

  2. Your team gets a context‑rich internal email so the first reply is informed, not “Can you send your store URL?”

  3. The customer gets an automatic acknowledgement that buys you time without feeling robotic.

That’s leverage. You design the flow once, then it scales with your install base without adding headcount.

Step 1: Turn “refund” and “cancel” into Custom Event triggers

The engine of this pattern is the Custom Event trigger.

When a merchant clicks “Request refund” in your app or cancels their plan in your billing system, your backend should send a custom event into Spreeflo via the JavaScript SDK or the Spreeflo API.

Most apps will want at least three events:

  • refund_requested

  • subscription_cancelled

  • refund_processed (used later when the refund is actually completed)

In Spreeflo, you then:

  1. Create a new journey from the Campaigns & Journeys area (you can read more about how these flows work in the guide on how to build a journey).

  2. Add a Custom Event trigger for refund_requested.

  3. Add a second Custom Event trigger for subscription_cancelled, also with Re-enrollment on.

  • Set Re-enrollment to on so repeat requests over the customer’s lifetime each run the journey once they’re out of it.

  • Optionally turn on property conditions if you want to scope it (for example, only where plan_type is paid).

Why two triggers instead of stuffing everything into one?

Because a refund for a still‑active user is a very different conversation from a pure cancellation. Splitting at the trigger level keeps each path simple and lets you adapt copy and internal alerts later without creating a tangle of conditions.

Behind the scenes, Spreeflo’s journey‑wide re‑enrollment lock protects you from duplicates: if a merchant fires refund_requested three times in an hour, they’ll still only go through the journey once at a time.

Step 2: Stamp the contact with refund or cancellation state

Once the trigger fires, your first job is to record what just happened.

That means a few quick Action nodes immediately after each trigger.

For `refund_requested` contacts

Right after the refund_requested Custom Event trigger, add:

  1. An Update Contact Attribute node to set a text attribute like refund_status to the literal "\"requested\"".

  2. A second Update Contact Attribute node for a timestamp attribute such as last_refund_requested_at, using the Set to now update type.

  3. An Add Tag node applying a tag like refund-open.

This takes seconds to configure, but it buys you a lot:

  • You can build segments like “refunds requested in the last 7 days” using the segment builder.

  • You can exclude refund-open contacts from promotional campaigns.

  • You can calculate how often a single merchant requests refunds over time.

These contact attributes and tags are static values you choose at design time, which makes them predictable and easy to reason about later.

For `subscription_cancelled` contacts

On the cancellation path, keep it just as clear:

  1. Use Update Contact Attribute to set subscription_status to "\"cancelled\"".

  2. Add an Add Tag node with something like cancelled.

If you track MRR or plan tier on the contact (for example, via your billing system calling Spreeflo.identify), those attributes will become useful a bit later when you decide which cancellations deserve a human reach‑out.

By the end of Step 2, every refund or cancellation has updated state on the contact record. Even if nothing else happened, you’ve already reduced operational chaos.

Step 3: Fire a context‑rich internal email to support

Now we get to the core of the pattern: the internal handoff.

On both paths, drop in a Send Internal Email node right after you’ve tagged and updated attributes.

For CartWizard, that internal email looks something like:

  • Subject: “Refund requested – [Store name] on [Plan]”

  • Body: store URL, app plan, monthly spend, install date, last 5 key events, and the reason provided in the app (if you’re syncing it as an attribute).

The goal is that whoever opens that email has enough context to reply thoughtfully without tab‑bing through Stripe, Shopify, and your own admin just to piece together the story.

In the node:

  • Choose your sender (e.g., support@cartwizard.io).

  • Point it at your shared support inbox.

  • Keep “Send only once” on so if your backend accidentally fires the same event twice, the support team doesn’t get spammed.

For teams already using a dedicated support tool, you have two options:

  • Pipe this internal email into the tool’s “email in” address so each event becomes a ticket with rich context.

  • Or, add a Webhook node immediately after the internal email. Use it to POST the contact data to any endpoint that creates tickets in your system of choice.

That Webhook step is optional and requires the Professional plan, but it can completely remove the “copy this into Zendesk” step from someone’s day.

Step 4: Send the customer a fast, clear acknowledgement

Internal handoff without customer acknowledgement still feels slow from the outside.

Right after the Send Internal Email on each path, add a Send Email node aimed at the contact. This is your “we’ve got you” moment.

Using Spreeflo’s drag‑and‑drop email builder, you might set up two templates:

  1. Refund acknowledgement

  2. Cancellation confirmation

  • “We’ve received your refund request for [Plan].”

  • When they can expect the refund to hit their account.

  • What happens to their access in the meantime.

  • A link to your billing or refund policy.

  • “We’ve cancelled your subscription; you’ll retain access until [date].”

  • How to reinstall or reactivate if they change their mind.

  • A quick one‑question survey on why they left (even just as a reply prompt).

In the Send Email node config:

  • Choose the relevant template.

  • Keep “Send only once” turned on. Because your trigger allows re‑enrollment, this prevents a trigger glitch from sending the same acknowledgement multiple times over the lifespan of the contact.

Notice the ordering here: internal email first, then customer email. That gives your team a head start while still acknowledging the customer quickly.

And because the internal and external messages are different channels, you avoid any customer‑facing spamminess.

Step 5: Wait for resolution, then close the loop (refunds only)

For refund requests, you usually don’t want to send a “your refund is complete” email until finance or Stripe has actually processed it.

Instead of guessing at timing with a fixed delay, this is where Spreeflo’s Wait Condition and If/Else nodes earn their keep.

On the refund_requested path, after the acknowledgement email:

  1. Add a Wait Condition action.

  2. Next, add an If/Else process node with the exact same condition:

  • Condition: Custom Event refund_processed triggered at least once in the last 3 days.

  • Timeout: 2 days.

Under the hood, this uses the same segment builder you used earlier, but embedded in a Wait Condition node. Spreeflo will pause the contact here until:

  • Your backend fires a refund_processed event for this contact, or

  • 2 days pass with no such event.

  • Condition: Custom Event refund_processed triggered at least once in the last 3 days.

  • “Yes” branch: they got their refund.

  • “Else” branch: nothing yet.

This gives you two clean outcomes.

“Yes” branch: refund completed

On the “yes” side of the If/Else:

  1. Add a Send Email node for your “Refund processed” confirmation.

  2. Follow it with:

  • An Update Contact Attribute setting refund_status to "\"processed\"".

  • A Remove Tag node removing refund-open.

From the customer’s perspective, they get exactly two emails about this refund: an acknowledgement when they ask, and a confirmation when it’s done. From your team’s perspective, you’ve also cleared the refund from your open‑cases view.

Structurally, you’ve respected pacing rules: there’s a Wait Condition plus the If/Else between the acknowledgement email and the final confirmation, so no path ever hits two Send Email nodes back to back.

“Else” branch: escalation

On the “else” side — when 2 days pass with no refund_processed event — automation nudges your team again.

Here, add another Send Internal Email node, this time to a founder or support lead:

  • Subject: “Refund pending > 48h – [Store name]”

  • Body: a brief reminder plus a link to your admin.

You might also add an Add Tag like refund-overdue so you can quickly report on how often this happens.

You’ll notice there’s no additional customer email here. At this point, you want a human looking at the edge case before replying.

Step 6: Treat cancellations as a churn signal, not just a receipt

On the subscription_cancelled path, the core pattern is the same — record state, internal handoff, acknowledgement. But you can take advantage of the fact that not all churn is equal.

Right after you tag the contact as cancelled, insert an If/Else node with a condition such as:

  • Contact attribute mrr greater than 200

On the “high‑value” branch:

  1. Add a Send Internal Email to the founder or customer success lead: “High‑MRR cancellation – [Store name].”

  2. Then send the standard cancellation confirmation to the customer.

On the “everyone else” branch:

  • Skip the extra internal step and go straight to the confirmation email.

This is a small change in the journey canvas, but it enforces a powerful rule: humans only get pulled into the cancellations where a personal outreach actually moves the needle.

For a team of five, that can be the difference between drowning in cancellation chatter and focusing on the dozen stores that really drive your ARR.

How this journey fits with your data and analytics

Because every state change is stored on the contact, you can use Spreeflo’s web tracking and analytics and audience tools to answer questions that usually require a BI stack:

  • How many refunds were requested last month?

  • What percentage of cancellations came from stores with more than 20 visits to your “billing FAQ” page?

  • Do merchants who engage with support before cancelling churn at a lower rate?

You can also create “operational” segments for day‑to‑day work:

  • tag is refund-open – open refunds that haven’t been processed.

  • subscription_status is cancelled AND refund_status is not processed – churned users still waiting on money back.

From there, it’s easy to build a journey or one‑off campaign that, for example, sends a weekly internal digest of unresolved refunds to the team.

Why this earns its retainer line item

If you’re running a Shopify app at $50k–$200k MRR, you probably see a steady trickle of refunds and cancellations every week.

Manually triaging each one costs:

  • 5–10 minutes of context‑gathering per event.

  • Another few minutes to draft a reply when you’re already mentally elsewhere.

  • Occasional mistakes — missed emails, slow replies, or someone forgetting to actually process the refund.

Multiply that by 40–50 events a month and you are quietly burning hours of founder or senior‑team time on something that follows the same pattern every single time.

This journey turns that pattern into a system:

  • Your app emits events.

  • Spreeflo does the mechanical work of tagging, alerting, and acknowledging.

  • Your team focuses only on the judgement calls.

That’s the essence of the second brand belief: founder‑led businesses win on leverage, not headcount. You don’t need a bigger support team; you need better defaults.

And because this flow runs on the same platform you already use for onboarding and lifecycle email, it’s cheap in both dollars and attention. If the journey helps you retain even one extra $199/mo customer or save a couple of App Store reviews, it has already paid for many months of Spreeflo’s pricing.

Design it once, connect your events to the Spreeflo API, and let the sequence at the top of this page quietly absorb the chaos the next time someone clicks “Cancel.”