Building a Custom Shopify Return Portal with Make.com
Build a custom Shopify return portal using Make.com that rivals Loop and Returnly at a fraction of the cost. Covers eligibility logic, return label generation, SMS updates via Twilio, Klaviyo win-back flows, and cost comparison.
If you are running a Shopify store doing more than $1M in annual revenue, you have probably looked at return portal apps and had the same reaction: $200-$500 per month for what is essentially a form, some conditional logic, and a shipping label. Loop Returns starts at $155/month and climbs quickly with volume. Returnly (now Affirm Returns) charges per return. AfterShip Returns starts reasonable but nickel-and-dimes you on every feature beyond basic label generation.
For many brands, the math does not work. You are paying $3,000-$6,000 per year for a tool that handles a workflow you could automate yourself -- if you knew how to wire it all together.
This guide walks through building a complete custom return portal using Make.com as the workflow engine, connected to Shopify, EasyPost (or Shippo) for labels, Twilio for SMS notifications, and Klaviyo for post-return marketing. The total infrastructure cost is under $50/month for most brands, and you get complete control over the customer experience, eligibility rules, and data flow.
What the Return Portal Does
Before we dive into the build, here is the complete feature set we are building:
- Customer-facing return form -- embedded in your Shopify store as a standalone page.
- Order lookup and validation -- customer enters order number and email, the system verifies the order exists and is eligible for return.
- Item-level return selection -- customer selects which items to return and provides a reason.
- Eligibility engine -- rules-based logic that checks return window, product exclusions, final sale tags, and previous return history.
- Return label generation -- automatic prepaid return label created via EasyPost or Shippo.
- SMS updates -- personalized status updates at each stage via Twilio (return approved, label generated, return received, refund processed).
- Shopify order tagging -- order is tagged with return status for internal visibility and reporting.
- Klaviyo flow triggers -- post-return win-back sequences personalized by return reason.
- Google Sheets logging -- every return is logged for analytics and customer service reference.
Part 1: The Customer-Facing Form
Option A: Shopify Page with Embedded Form (Recommended)
Create a new page in your Shopify store at /pages/returns. Use Shopify's custom Liquid template to embed a form that posts to a Make.com webhook. This keeps the return portal on your domain, within your store's design system.
Create a new template in your Shopify theme called page.returns.liquid:
<div id="return-portal" class="page-width">
<h1>Start a Return</h1>
<p>Enter your order details below. You can find your order number in your confirmation email.</p>
<form id="return-lookup-form">
<div class="field">
<label for="order-number">Order Number</label>
<input type="text" id="order-number" name="order_number"
placeholder="#1001" required />
</div>
<div class="field">
<label for="order-email">Email Address</label>
<input type="email" id="order-email" name="email"
placeholder="you@example.com" required />
</div>
<button type="submit" class="button button--primary">
Look Up Order
</button>
</form>
<div id="return-items" style="display:none;">
<!-- Populated dynamically after order lookup -->
</div>
<div id="return-confirmation" style="display:none;">
<h2>Return Submitted</h2>
<p>Check your email and phone for your prepaid return label.</p>
</div>
</div>
<script>
const MAKE_WEBHOOK_LOOKUP = "https://hook.us1.make.com/YOUR_LOOKUP_WEBHOOK_ID";
const MAKE_WEBHOOK_SUBMIT = "https://hook.us1.make.com/YOUR_SUBMIT_WEBHOOK_ID";
document.getElementById("return-lookup-form").addEventListener("submit", async (e) => {
e.preventDefault();
const orderNumber = document.getElementById("order-number").value.replace("#", "");
const email = document.getElementById("order-email").value;
const response = await fetch(MAKE_WEBHOOK_LOOKUP, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ order_number: orderNumber, email: email }),
});
const data = await response.json();
if (data.eligible) {
renderReturnItems(data.items);
document.getElementById("return-items").style.display = "block";
} else {
alert(data.message || "This order is not eligible for returns.");
}
});
function renderReturnItems(items) {
const container = document.getElementById("return-items");
container.innerHTML = `
<h2>Select Items to Return</h2>
<form id="return-submit-form">
${items.map(item => `
<div class="return-item">
<label>
<input type="checkbox" name="items" value="${item.line_item_id}" />
${item.title} - ${item.variant_title} (Qty: ${item.quantity})
</label>
<select name="reason_${item.line_item_id}">
<option value="">Select reason...</option>
<option value="too_small">Too Small</option>
<option value="too_large">Too Large</option>
<option value="defective">Defective/Damaged</option>
<option value="not_as_described">Not As Described</option>
<option value="changed_mind">Changed My Mind</option>
</select>
</div>
`).join("")}
<button type="submit" class="button button--primary">Submit Return</button>
</form>
`;
document.getElementById("return-submit-form").addEventListener("submit", async (e) => {
e.preventDefault();
// Collect selected items and reasons, submit to Make.com
const formData = new FormData(e.target);
const selectedItems = formData.getAll("items").map(id => ({
line_item_id: id,
reason: formData.get(`reason_${id}`),
}));
await fetch(MAKE_WEBHOOK_SUBMIT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
order_number: document.getElementById("order-number").value.replace("#", ""),
email: document.getElementById("order-email").value,
items: selectedItems,
}),
});
document.getElementById("return-items").style.display = "none";
document.getElementById("return-confirmation").style.display = "block";
});
}
</script>
Option B: Standalone Typeform or Tally.so Form
If you do not want to touch your Shopify theme, use a form tool like Tally.so (free) or Typeform. Embed it in a Shopify page via iframe or link to it from your order confirmation email. The form submits to the same Make.com webhook. The tradeoff is less design control and a slightly disjointed customer experience.
Part 2: The Make.com Workflows
You need two Make.com scenarios: one for order lookup (synchronous, returns data to the form) and one for return submission (asynchronous, triggers the full return process).
Scenario 1: Order Lookup and Eligibility Check
Trigger: Custom Webhook (receives order_number and email)
Step 1: Search Shopify Orders. Use Make.com's Shopify module to search for the order by order number. Verify the email matches the order's contact email.
Step 2: Check Eligibility. This is a Router module with multiple paths:
Path 1: Order not found
-> Webhook Response: { eligible: false, message: "Order not found. Please check your order number." }
Path 2: Order found but outside return window
-> Condition: (now - order.created_at) > 30 days
-> Webhook Response: { eligible: false, message: "This order is past the 30-day return window." }
Path 3: Order has "final-sale" tag
-> Condition: order.tags contains "final-sale"
-> Webhook Response: { eligible: false, message: "Final sale items are not eligible for returns." }
Path 4: Order already has a return in progress
-> Condition: order.tags contains "return-pending" OR "return-approved"
-> Webhook Response: { eligible: false, message: "A return is already in progress for this order." }
Path 5: Order is eligible
-> Webhook Response: {
eligible: true,
items: [mapped line items with id, title, variant_title, quantity]
}
The 30-day return window is configurable. Some brands use 14 days, others 60. You can also make it product-specific by checking product tags (e.g., "return-window-14" vs. "return-window-60").
Scenario 2: Return Submission and Processing
This is the main workflow. It fires when the customer submits their return request and runs asynchronously (the customer sees a confirmation immediately; the processing happens in the background).
Trigger: Custom Webhook (receives order_number, email, items array with reasons)
Step 1: Fetch full order data from Shopify. The webhook payload is minimal. Pull the complete order object including shipping address, line item details, and financial data.
Step 2: Route by return reason. Use a Router module to apply different logic based on the return reason:
- Defective/Damaged: Auto-approve immediately. No return shipping required (let the customer keep the item). Issue a full refund or replacement. This is a cost-of-doing-business decision that improves customer satisfaction dramatically.
- Size Issue (too small/too large): Auto-approve. Generate return label. Optionally offer an exchange instead of refund.
- Changed Mind: Auto-approve if within return window. Generate return label. Deduct return shipping cost from refund amount if your policy requires it.
- Not As Described: Auto-approve. Flag for QA review (send a Slack notification to your product team). Generate return label.
Ready to Automate This?
One subscription, unlimited automation requests. From workflow builds to AI agents — we handle it all. No hiring, no contracts, no surprises.
Step 3: Generate Return Shipping Label
Use EasyPost or Shippo to generate a prepaid return label. Here is the EasyPost flow in Make.com:
HTTP Module: Create EasyPost Shipment
{
"shipment": {
"to_address": {
"name": "Returns Department",
"street1": "123 Warehouse Blvd",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US",
"phone": "555-0100"
},
"from_address": {
"name": "{{customer_name}}",
"street1": "{{customer_street}}",
"city": "{{customer_city}}",
"state": "{{customer_state}}",
"zip": "{{customer_zip}}",
"country": "{{customer_country}}",
"phone": "{{customer_phone}}"
},
"parcel": {
"predefined_package": "FlatRateEnvelope",
"weight": 16
}
}
}
HTTP Module: Buy EasyPost Rate
Select the cheapest rate from the shipment response and purchase it. EasyPost returns a label URL that you can send directly to the customer.
Cost: EasyPost charges no per-label markup -- you pay carrier rates directly. A USPS Priority Mail return label typically costs $7-$12, which is significantly less than the $4-$6 per-return fee that Loop and Returnly charge on top of label costs.
Step 4: Send SMS Updates via Twilio
Personalized SMS based on Shopify order tags and return status is one of the highest-impact features of a custom return portal. SMS open rates are 98% vs. 20% for email, and return status is exactly the kind of time-sensitive information customers want via text.
In Make.com, use the Twilio module to send messages at each stage:
Return Approved:
Hi {{first_name}}, your return for Order {{order_name}} has been approved!
Your prepaid return label is here: {{label_url}}
Print it and drop off your package at any USPS location.
Return Received (triggered by carrier tracking webhook):
We've received your return for Order {{order_name}}.
Your refund of {{refund_amount}} will be processed within 3-5 business days.
Refund Processed:
Your refund of {{refund_amount}} for Order {{order_name}} has been processed.
It may take 5-10 days to appear on your statement. Thanks for being a customer!
The Twilio cost is approximately $0.0079 per SMS in the US. For a brand processing 200 returns/month with 3 messages per return, that is $4.74/month.
To implement personalized SMS based on Shopify order tags, the Make.com workflow reads the order's tags and adjusts the message accordingly. For example, if the order has a "VIP" tag, the message might include a discount code for their next purchase. If the order has a "wholesale" tag, the message routes to a different return process entirely.
Step 5: Update Shopify Order Tags
As the return progresses, update the Shopify order with tags that reflect the current status. This gives your customer service team instant visibility in the Shopify admin:
return-pending-- return request submitted, awaiting processingreturn-approved-- approved, label generatedreturn-in-transit-- carrier scan detected (via tracking webhook)return-received-- warehouse confirmed receiptreturn-refunded-- refund issuedreturn-reason:defective-- reason tag for analytics
Use Make.com's Shopify "Update Order" module. Note that Shopify tags are a comma-separated string, not an array. Your workflow needs to append the new tag to the existing tags, not replace them:
New tags: {{existing_tags}}, return-approved, return-reason:{{reason}}
Step 6: Trigger Klaviyo Flows for Win-Back
This is where a custom return portal pays for itself. Instead of treating every return as a loss, you trigger targeted Klaviyo flows based on the return reason:
Size Issue Returns: Trigger a Klaviyo flow that sends a "we want to get your size right" email 3 days after the refund. Include a link to your size guide and a 10% discount code for their next order. Conversion rate on these flows: 15-25% in our experience.
Changed Mind Returns: Trigger a flow 7 days later with curated product recommendations based on what they originally ordered. "Still looking for the perfect [product category]? Here are some alternatives you might love."
Defective Returns: Trigger an immediate apology flow with a higher-value discount (15-20%). These customers had a bad experience and need extra incentive to come back.
The Klaviyo integration works via webhook. In Make.com, use an HTTP module to hit Klaviyo's Track API:
{
"token": "YOUR_KLAVIYO_PUBLIC_KEY",
"event": "Return Completed",
"customer_properties": {
"$email": "{{customer_email}}",
"$phone_number": "{{customer_phone}}",
"$first_name": "{{first_name}}"
},
"properties": {
"OrderNumber": "{{order_number}}",
"ReturnReason": "{{return_reason}}",
"ReturnItems": "{{returned_item_titles}}",
"RefundAmount": "{{refund_amount}}",
"OriginalOrderValue": "{{original_order_total}}"
}
}
This same webhook-to-Klaviyo pattern applies to other lead sources. If you are running Facebook Lead Ads, you can sync those leads to Klaviyo via webhook using an identical Make.com workflow: Facebook Lead Ads trigger fires, Make.com receives the lead data, transforms it, and pushes it to Klaviyo's API. The lead enters a welcome flow segmented by the ad they responded to. It is the same architecture -- webhook in, transform, API out -- just with different source and destination nodes.
Step 7: Google Sheets Logging
Every return writes a row to a master Google Sheet with the following columns:
| Date | Order # | Customer | Items Returned | Reason | Return Value | Label Cost | Label Tracking | Status | Xero Credit Note | Notes |
|---|
This sheet becomes your returns dashboard. Build a simple pivot table or connect it to Looker Studio for visualizations: return rate by product, return rate by reason, average return processing time, return shipping costs, and recovery rate from win-back flows.
Cost Comparison: Custom Portal vs. SaaS Return Apps
Here is what the custom portal costs for a brand processing 200 returns per month:
| Component | Monthly Cost |
|---|---|
| Make.com (Core plan, 10,000 operations) | $10.59 |
| EasyPost (no monthly fee, pay per label) | $0 |
| Return shipping labels (~200 x $9 avg) | $1,800 |
| Twilio SMS (~600 messages) | $4.74 |
| Klaviyo (already paying for this) | $0 |
| Google Sheets | $0 |
| Total infrastructure | ~$15.33/mo |
| Total including labels | ~$1,815/mo |
Now compare to Loop Returns:
| Component | Monthly Cost |
|---|---|
| Loop Returns (Growth plan) | $340 |
| Return shipping labels (~200 x $9 avg) | $1,800 |
| Per-return fee ($2.50 x 200) | $500 |
| Total | ~$2,640/mo |
The custom portal saves approximately $825/month ($9,900/year), and that gap widens as return volume increases because the per-return fees on SaaS platforms compound while Make.com's operation costs grow linearly and slowly.
The one thing you give up is the polished UI that Loop and similar platforms provide. Their customer-facing portal is genuinely well-designed. But if your brand has even basic frontend capabilities, you can build a return form that matches your store's design system, which is actually a better customer experience than redirecting to a third-party domain.
When This Approach Is Not Enough
The custom portal works well for brands with straightforward return policies and moderate volume (up to 500-1,000 returns/month). It starts to strain when:
- You need in-store returns. POS return flows require Shopify POS integration that Make.com does not handle natively.
- You need instant exchanges. Sending a replacement before the return is received requires inventory reservation logic and payment authorization holds that go beyond what a Make.com scenario can manage.
- You process 2,000+ returns/month. At this volume, you need dedicated return management infrastructure with warehouse scanning integrations, quality grading workflows, and restocking automation.
For those cases, a dedicated return app or a custom-built system is the right call. But for the vast majority of Shopify brands in the $1M-$20M range, this Make.com-based portal handles the use case fully and saves thousands per year.
At Futureman Labs, we typically build and deploy this complete return portal system in 5-7 business days. The initial build includes the form, all Make.com scenarios, Twilio configuration, Klaviyo flow setup, and the Google Sheets dashboard. Ongoing support is minimal -- the system runs autonomously, and we monitor it as part of our managed automation service.
Want to Talk Through Your Automation Needs?
Book a 30-minute call. We'll map out which automations would save you the most time — no obligation.
Want to Talk Through Your Automation Needs?
Book a 30-minute call. We'll map out which automations would save you the most time — no obligation.
Related Articles
Connect Airtable to Shopify for Product Management: The Enterprise-Grade Way
Learn how to build a robust, bidirectional sync between Airtable and Shopify for product management using n8n. Covers variant sync, image management, Shopify API rate limits, and why Zapier breaks at scale.
How to Automatically Generate and Send Invoices from Shopify Orders to Xero
A complete technical guide to building an automated invoicing pipeline from Shopify to Xero. Covers tax mapping, multi-currency, refund handling, duplicate prevention, and automatic PDF invoice generation.