---
title: "Discount code removed alerts"
description: "Set up a Shopify Flow workflow for Discount Code Removed and send the impact summary to Slack with a webhook-based Slack workflow."
canonical: "https://docs.workflow-trigger-extensions.app/discount-code-removed-alerts"
---

# 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.

> [!NOTE]
> This setup uses **Workflow 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 `text` variable 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 > Workflow 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 `text` field.

## Video Guide

You can see the full implementation video as well.

[Video](https://youtu.be/pIcNyEiHMtc)

### 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.

![Slack workflow that starts with a webhook and sends the text variable to a Slack channel](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/072ecf064f63c80e.png)
*Slack workflow that starts with a webhook and sends the text variable to a Slack channel*

> [!TIP]
> Name the Slack variable `text` to keep the Shopify Flow request body simple.

## Grant Discount Access in Workflow Trigger Extensions

Before the trigger appears in Shopify Flow, enable discount access in the app.

In Shopify admin, go to **Apps > Workflow 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**.

![Workflow Trigger Extensions dashboard showing Grant Discount Access enabled](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/4ad588c1a6991709.png)
*Workflow Trigger Extensions dashboard showing Grant Discount Access enabled*

## Build the Shopify Flow

Open **Shopify Flow** and create a new workflow.

Search for discount-related triggers and select **Discount Code Removed** from **Workflow Trigger Extensions**.

![Shopify Flow trigger picker with Discount Code Removed selected](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/f961d49392f00838.png)
*Shopify Flow trigger picker with Discount Code Removed selected*

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/'}}"
```

![Get discount data action using an advanced query with the cleaned discount ID](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/714f654da91533e8.png)
*Get discount data action using an advanced query with the cleaned discount ID*

> [!NOTE]
> 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,
  };
}
```

![Run code step in Shopify Flow with the query, outputs, and JavaScript report builder](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/e74c533667bf2b18.png)
*Run code step in Shopify Flow with the query, outputs, and JavaScript report builder*

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.

![HTTP Request action in Shopify Flow sending the runCode summary in the text field](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/40b77cf4b12ea298.png)
*HTTP Request action in Shopify Flow sending the runCode summary in the text field*

> [!NOTE]
> 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](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.

![Slack message showing the discount code removed summary](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/47137d41070f34ca.png)
*Slack message showing the discount code removed summary*

## 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 > Workflow 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.

> [!WARNING]
> Deleting a discount code is a real change in Shopify. Test with a disposable code rather than a live campaign code.
