Discount code removed alerts
When to use this workflow
Use this workflow when a discount code is removed and you need an immediate record of what was affected. It helps you capture the removed code, its usage count, the campaign details, and a summary you can send to finance or operations in Slack.
This is especially useful for leaked influencer codes, unplanned coupon exposure, or any case where you need a documented impact report after a code is removed.
This setup uses Flow Trigger Extensions for the Discount Code Removed trigger, Shopify Flow for orchestration, and a Slack workflow that Starts with a webhook.
What you will build
A Slack workflow that receives a
textvariable from a webhook and posts that same text into a Slack channel.A Shopify Flow workflow triggered by Discount Code Removed.
A Get discount data step that looks up the discount using the ID from the trigger.
A Run code step that formats a removal report.
An HTTP Request step that sends the report to Slack.
Before you begin
Make sure the app is installed and visible in Shopify admin under Apps > Flow Trigger Extensions.
Make sure you have access to Shopify Flow.
Create or choose a Slack channel where alerts should appear.
Create a Slack workflow that starts with a webhook and accepts a
textfield.
Video Guide
You can see the full implementation video as well.
Create the Slack workflow first
In Slack, create a workflow with the trigger Starts with a webhook. Add a text variable named text, then add the message step that sends a message to your chosen channel using that same text variable.

Name the Slack variable text to keep the Shopify Flow request body simple.
Grant Discount Access in Flow Trigger Extensions
Before the trigger appears in Shopify Flow, enable discount access in the app.
In Shopify admin, go to Apps > Flow Trigger Extensions.
On the Dashboard, look for the setup checklist.
Turn on Grant Discount Access. This enables discount-related triggers such as Discount Created, Discount Updated, Discount Deleted, Discount Code Added, Discount Code Removed, and Discount Expired.

Build the Shopify Flow
Open Shopify Flow and create a new workflow.
Search for discount-related triggers and select Discount Code Removed from Flow Trigger Extensions.

Add the Shopify action Get discount data.
In Select a query to filter data, choose Advanced. In Edit query, use the discount ID from the trigger and remove the Shopify GID prefix:
id:"{{discount.id|remove:'gid://shopify/DiscountCodeNode/'}}"
This step uses the discount identifier from the trigger so the workflow can fetch the matching discount campaign details.
Add a Run code step after Get discount data.
Use the following GraphQL query in Select inputs from previous steps:
query {
redeemCode {
code
usageCount
bulkRemoval
}
getDiscountData {
discount {
... on DiscountCodeBasic {
title
startsAt
endsAt
asyncUsageCount
codes {
code
asyncUsageCount
}
customerGets {
value {
... on DiscountAmount {
amount { amount currencyCode }
}
... on DiscountPercentage {
percentage
}
}
}
}
}
}
}Define the outputs like this:
"Output of discount code removal report"
type Output {
"Discount code that was removed"
code: String!
"Discount campaign title"
title: String!
"Campaign start date"
startsAt: String!
"Campaign end date"
endsAt: String!
"Number of times it was redeemed"
usageCount: Int!
"Total discount impact as a formatted string"
totalImpact: String!
"When it was removed"
removedAt: String!
"Full summary for alerts"
summary: String!
}Then paste this code into Write code:
export default function main(input) {
// ── Trigger: removed code ──
const removedCode = input?.redeemCode?.code ?? "Unknown";
const usageCount = input?.redeemCode?.usageCount ?? null; // null = bulk removal or unknown
const bulkRemoval = input?.redeemCode?.bulkRemoval ?? false;
const currency = input?.shop?.currencyCode ?? "";
const shopName = input?.shop?.name ?? "(not bound)";
const removedAt = new Date().toISOString();
// ── Find matching discount ──
const dataItems = input?.getDiscountData ?? [];
let matchedDiscount = null;
for (const item of dataItems) {
const d = item?.discount?.DiscountCodeBasic ?? item?.discount;
if (!d) continue;
const codesArray = Array.isArray(d.codes)
? d.codes.map(c => (typeof c === "string" ? c : c?.code ?? ""))
: [];
if (codesArray.includes(removedCode)) {
matchedDiscount = d;
break;
}
}
// fallback to first entry if removed code already gone from list
if (!matchedDiscount && dataItems.length > 0) {
matchedDiscount =
dataItems[0]?.discount?.DiscountCodeBasic ?? dataItems[0]?.discount ?? null;
}
const title = matchedDiscount?.title ?? "Untitled";
const startsAt = matchedDiscount?.startsAt ?? "N/A";
const endsAt = matchedDiscount?.endsAt ?? "None (no expiry)";
const totalUsageCount = matchedDiscount?.asyncUsageCount ?? null;
// ── Discount value ──
const valueWrapper = matchedDiscount?.customerGets?.value ?? {};
const pctObj = valueWrapper?.DiscountPercentage ?? null;
const amtObj = valueWrapper?.DiscountAmount ?? valueWrapper?.amount ?? null;
let discountLabel = "Unknown";
if (pctObj?.percentage != null) {
discountLabel = `${(pctObj.percentage * 100).toFixed(0)}% off`;
} else if (amtObj?.amount != null) {
const cur = amtObj.currencyCode || currency;
discountLabel = `${cur} ${parseFloat(amtObj.amount).toFixed(2)} off`;
}
// ── This code's impact ──
let thisCodeImpact;
if (bulkRemoval) {
thisCodeImpact = `Not available — removed as part of bulk deletion`;
} else if (usageCount === null) {
thisCodeImpact = `Could not be determined`;
} else if (pctObj?.percentage != null) {
thisCodeImpact = `${discountLabel} × ${usageCount} use(s)`;
} else if (amtObj?.amount != null) {
const perUse = parseFloat(amtObj.amount);
const cur = amtObj.currencyCode || currency;
thisCodeImpact = `${cur} ${(perUse * usageCount).toFixed(2)} (${cur} ${perUse.toFixed(2)} × ${usageCount} uses)`;
} else {
thisCodeImpact = "Unknown";
}
// ── Campaign-wide impact using asyncUsageCount on the discount ──
let campaignImpact;
if (totalUsageCount === null) {
campaignImpact = "Not available";
} else if (pctObj?.percentage != null) {
campaignImpact = `${discountLabel} × ${totalUsageCount} total use(s) across all codes`;
} else if (amtObj?.amount != null) {
const perUse = parseFloat(amtObj.amount);
const cur = amtObj.currencyCode || currency;
campaignImpact = `${cur} ${(perUse * totalUsageCount).toFixed(2)} total (${cur} ${perUse.toFixed(2)} × ${totalUsageCount} uses)`;
} else {
campaignImpact = "Unknown";
}
// ── Per-code breakdown ──
const codesNodes = Array.isArray(matchedDiscount?.codes)
? matchedDiscount.codes
: [];
const allCodes = codesNodes
.map(c => (typeof c === "string" ? c : c?.code ?? ""))
.filter(Boolean);
const codesRoster = codesNodes.length
? codesNodes.map(c => {
const code = typeof c === "string" ? c : c?.code ?? "";
const used = c?.asyncUsageCount ?? "?";
const flag = code === removedCode ? " ← REMOVED" : "";
return `${code} (used ${used}×)${flag}`;
}).join("\n ")
: "N/A";
const usageCountLabel = bulkRemoval
? "N/A (bulk removal)"
: usageCount !== null
? String(usageCount)
: "Unknown";
const summary =
`DISCOUNT CODE REMOVED\n` +
`─────────────────────\n` +
`Store: ${shopName}\n` +
`Code removed: ${removedCode}\n` +
`Bulk removal: ${bulkRemoval ? "Yes" : "No"}\n` +
`Campaign: ${title}\n` +
`Discount: ${discountLabel}\n` +
`Valid from: ${startsAt}\n` +
`Valid until: ${endsAt}\n` +
`\n` +
`THIS CODE\n` +
` Times used: ${usageCountLabel}\n` +
` Impact: ${thisCodeImpact}\n` +
`\n` +
`CAMPAIGN TOTAL\n` +
` Total uses: ${totalUsageCount ?? "Unknown"}\n` +
` Total impact: ${campaignImpact}\n` +
`\n` +
`CODE BREAKDOWN\n` +
` ${codesRoster}\n` +
`\n` +
`Removed at: ${removedAt}`;
const safeSummary = summary
.replace(/\\/g, "\\\\") // escape backslashes first
.replace(/"/g, '\\"') // escape quotes
.replace(/\n/g, "\\n") // escape newlines
.replace(/\r/g, "\\r");
return {
code: removedCode,
bulkRemoval,
title,
discountLabel,
startsAt,
endsAt,
usageCount: usageCount ?? 0,
usageCountLabel,
thisCodeImpact,
totalUsageCount: totalUsageCount ?? 0,
campaignImpact,
allCodes,
codesRoster,
removedAt,
summary: safeSummary,
};
}
Add the HTTP Request action. In the Variables or Body Override (JSON) field, send the summary returned by the Run code step:
{
"body": {
"text": "{{runCode.summary}}"
}
}This posts the generated summary to the Slack webhook workflow using the text variable.

If your Shopify setup allows direct HTTP requests in Shopify Flow, you can send the webhook directly. If not, you can use the Flow Transactional Email action HTTP Request method described in the guide at https://docs.flow-action-extensions.app/make-an-http-request.
Save the workflow after all steps are in place.
To test, delete a discount code from your discount campaign and confirm the Slack channel receives the summary message.

What the Slack alert includes
The removed discount code
Whether it was a bulk removal
The campaign title
The discount value
Start and end dates
Times used for the removed code
Estimated impact for the removed code
Campaign-wide usage and impact
A code-by-code breakdown when available
The timestamp when the removal was processed
Troubleshooting
Go back to Apps > Flow Trigger Extensions and confirm Grant Discount Access is enabled. The discount triggers only appear after access is granted.
Double-check the advanced query in Edit query. The ID should be cleaned exactly as shown:
id:"{{discount.id|remove:'gid://shopify/DiscountCodeNode/'}}"Make sure the Slack workflow starts with a webhook, the webhook URL is the one used by your HTTP request, and the request body includes the text field.
Some removals may be part of a bulk action or may not return complete usage details. In those cases, the summary will still send the best available information and clearly label missing values.
Best practices
Send these alerts to a dedicated finance or operations channel instead of a general support channel.
Keep the workflow name specific, such as Discount Code Removed, so it is easy to identify in Shopify Flow and Slack.
Test with a non-production discount code first to confirm the message format and webhook behavior.
Deleting a discount code is a real change in Shopify. Test with a disposable code rather than a live campaign code.