> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useautumn.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Slack and Discord Notifications

> Send Autumn webhook events to Slack or Discord channels with rich, formatted messages using Svix transformations.

Forward Autumn webhook events to a Slack or Discord channel with ready-made
[Svix transformations](https://docs.svix.com/transformations) that turn each
event into a clean, formatted message.

## Supported events

| Event                            | Slack | Discord |
| -------------------------------- | :---: | :-----: |
| `customer.products.updated`      |   ✓   |    ✓    |
| `balances.limit_reached`         |   ✓   |    ✓    |
| `balances.usage_alert_triggered` |   ✓   |    ✓    |

Other event types — including Vercel Marketplace events — are skipped by the
transforms so they are never delivered to your Slack or Discord channel.

## Setup

<Steps>
  <Step title="Get an incoming webhook URL">
    Follow the official guide for the platform you want to use:

    * [Slack — Sending messages using incoming webhooks](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/)
    * [Discord — Intro to webhooks](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks)

    Both flows give you a webhook URL that looks like
    `https://hooks.slack.com/services/...` or
    `https://discord.com/api/webhooks/...`. Keep it handy for the next step.
  </Step>

  <Step title="Add the webhook endpoint in Autumn">
    In your Autumn dashboard, go to **Developer → Webhooks** and click
    **Add Endpoint**. Paste in the URL from the previous step and select the
    events you want to subscribe to.

    <Frame>
      <img src="https://mintcdn.com/autumn-b9b4c0fb/KozpDBOLdGK1YC2b/images/svix-notifications/01-add-endpoint.png?fit=max&auto=format&n=KozpDBOLdGK1YC2b&q=85&s=bc1dd0c4559fff18f97235c60e0639ff" alt="Adding a webhook endpoint in the Autumn dashboard" width="3434" height="1894" data-path="images/svix-notifications/01-add-endpoint.png" />
    </Frame>
  </Step>

  <Step title="Enable transformations">
    Open the endpoint you just created, switch to the **Advanced** tab, and
    click **Edit transformation**.

    <Frame>
      <img src="https://mintcdn.com/autumn-b9b4c0fb/KozpDBOLdGK1YC2b/images/svix-notifications/02-advanced-tab.png?fit=max&auto=format&n=KozpDBOLdGK1YC2b&q=85&s=bfb4a27d9172de0fed492b79e6150bd8" alt="The Advanced tab on a webhook endpoint, showing the Edit transformation button" width="2124" height="1774" data-path="images/svix-notifications/02-advanced-tab.png" />
    </Frame>

    Paste in the transform code below for the platform you're targeting,
    then click **Save and Enable**.

    <Frame>
      <img src="https://mintcdn.com/autumn-b9b4c0fb/KozpDBOLdGK1YC2b/images/svix-notifications/03-transformation-editor.png?fit=max&auto=format&n=KozpDBOLdGK1YC2b&q=85&s=3e4fe264300dad96d99ee956867896ad" alt="The transformation editor with the Enabled toggle, code area, and Save and Enable button highlighted" width="1388" height="822" data-path="images/svix-notifications/03-transformation-editor.png" />
    </Frame>
  </Step>

  <Step title="Enable throttling">
    Back on the **Advanced** tab, click **Edit** next to **Endpoint Throttling**
    and set a sensible RPS (requests-per-second) limit. This prevents your
    Slack or Discord channel from being flooded during high-volume events such
    as bulk customer migrations or backfills.

    <Frame>
      <img src="https://mintcdn.com/autumn-b9b4c0fb/KozpDBOLdGK1YC2b/images/svix-notifications/04-throttling.png?fit=max&auto=format&n=KozpDBOLdGK1YC2b&q=85&s=766b52309afaa62188b4f93311ee6316" alt="Endpoint throttling editor with an RPS input field" width="1300" height="240" data-path="images/svix-notifications/04-throttling.png" />
    </Frame>
  </Step>
</Steps>

## Transform code

Copy the transform for your platform into the **Code** editor on the
transformation page, then click **Save and Enable**.

<CodeGroup>
  {/* Source: others/svix-transforms/slack.ts */}

  ```js Slack theme={null}
  /**
   * @param webhook the webhook object
   * @param webhook.method destination method. Allowed values: "POST", "PUT"
   * @param webhook.url current destination address
   * @param webhook.eventType current webhook Event Type
   * @param webhook.payload JSON payload
   * @param webhook.cancel whether to cancel dispatch of the given webhook
   */
  function handler(webhook) {
    var AUTUMN_BASE = "https://app.useautumn.com/customers/";
    var AUTUMN_USERNAME = "Autumn";
    var AUTUMN_ICON_URL = "https://i.ibb.co/BHCF1ZqL/autumnicon.png";

    var payload = webhook.payload || {};
    var data = payload.data || payload;

    // ============ customer.products.updated ============
    if (webhook.eventType === "customer.products.updated") {
      var scenario = data.scenario || "updated";
      var customer = data.customer || {};
      var entity = data.entity || null;
      var product = data.updated_product || {};

      var customerName = customer.name || customer.email || customer.id || "Customer";
      var customerEmail = customer.email || null;
      var customerId = customer.id || "";

      var productName = product.name || "their plan";
      if (product.version && product.version !== 1) {
        productName = productName + " V" + product.version;
      }

      var entityLabel = null;
      if (entity) {
        entityLabel = entity.name || entity.id || null;
      }

      var meta = {
        "new":        { emoji: "🎉", header: "New Subscription",         verb: "subscribed to" },
        "upgrade":    { emoji: "🚀", header: "Customer Upgraded",        verb: "upgraded to" },
        "downgrade":  { emoji: "📉", header: "Customer Downgraded",      verb: "downgraded to" },
        "cancel":     { emoji: "⚠️",  header: "Subscription Cancelled",   verb: "cancelled" },
        "renew":      { emoji: "🔄", header: "Subscription Uncancelled", verb: "uncancelled" },
        "expired":    { emoji: "💀", header: "Subscription Expired",     verb: "expired on" },
        "scheduled":  { emoji: "📅", header: "Change Scheduled",         verb: "scheduled a change to" }
      }[scenario] || { emoji: "🔔", header: "Subscription Updated", verb: "updated" };

      var sentence;
      if (scenario === "expired") {
        sentence = "*" + customerName + "*'s *" + productName + "* expired";
      } else {
        sentence = "*" + customerName + "* " + meta.verb + " *" + productName + "*";
      }

      var previewText = meta.emoji + " " + customerName + " " + meta.verb + " " + productName;

      var fields = [
        { type: "mrkdwn", text: "*Customer:*\n" + customerName }
      ];
      if (customerEmail) {
        fields.push({ type: "mrkdwn", text: "*Email:*\n" + customerEmail });
      }
      fields.push({ type: "mrkdwn", text: "*Product:*\n" + productName });
      fields.push({ type: "mrkdwn", text: "*Scenario:*\n`" + scenario + "`" });
      if (entityLabel) {
        fields.push({ type: "mrkdwn", text: "*Entity:*\n" + entityLabel });
      }

      var contextParts = [];
      if (customerId) {
        contextParts.push("Customer ID: `" + customerId + "`");
      }
      if (entity && entity.id) {
        contextParts.push("Entity: `" + entity.id + "`");
      }

      var blocks = [
        {
          type: "header",
          text: { type: "plain_text", text: meta.emoji + " " + meta.header, emoji: true }
        },
        {
          type: "section",
          text: { type: "mrkdwn", text: sentence }
        },
        {
          type: "section",
          fields: fields
        }
      ];

      if (customerId) {
        blocks.push({
          type: "actions",
          elements: [
            {
              type: "button",
              text: { type: "plain_text", text: "View in Autumn", emoji: true },
              url: AUTUMN_BASE + customerId,
              style: "primary"
            }
          ]
        });
      }

      if (contextParts.length > 0) {
        blocks.push({
          type: "context",
          elements: [
            { type: "mrkdwn", text: contextParts.join(" | ") }
          ]
        });
      }

      webhook.payload = {
        username: AUTUMN_USERNAME,
        icon_url: AUTUMN_ICON_URL,
        text: previewText,
        blocks: blocks
      };
      return webhook;
    }

    // ============ balances.limit_reached ============
    if (webhook.eventType === "balances.limit_reached") {
      var lrCustomerId = data.customer_id || "";
      var lrFeatureId = data.feature_id || "feature";
      var lrLimitType = data.limit_type || "included";
      var lrEntityId = data.entity_id || null;

      var lrCustomerLink = lrCustomerId
        ? "<" + AUTUMN_BASE + lrCustomerId + "|`" + lrCustomerId + "`>"
        : "`unknown`";

      var lrSentence =
        lrCustomerLink + " hit their *" + lrFeatureId + "* `" + lrLimitType + "` limit";

      var lrPreview = "🚫 " + lrCustomerId + " hit their " + lrFeatureId + " limit";

      var lrFields = [
        { type: "mrkdwn", text: "*Customer:*\n" + lrCustomerLink },
        { type: "mrkdwn", text: "*Feature:*\n`" + lrFeatureId + "`" },
        { type: "mrkdwn", text: "*Limit Type:*\n`" + lrLimitType + "`" }
      ];
      if (lrEntityId) {
        lrFields.push({ type: "mrkdwn", text: "*Entity:*\n`" + lrEntityId + "`" });
      }

      var lrContextParts = [];
      if (lrCustomerId) lrContextParts.push("Customer ID: `" + lrCustomerId + "`");
      if (lrEntityId) lrContextParts.push("Entity: `" + lrEntityId + "`");

      var lrBlocks = [
        {
          type: "header",
          text: { type: "plain_text", text: "🚫 Limit Reached", emoji: true }
        },
        {
          type: "section",
          text: { type: "mrkdwn", text: lrSentence }
        },
        {
          type: "section",
          fields: lrFields
        }
      ];

      if (lrCustomerId) {
        lrBlocks.push({
          type: "actions",
          elements: [
            {
              type: "button",
              text: { type: "plain_text", text: "View in Autumn", emoji: true },
              url: AUTUMN_BASE + lrCustomerId,
              style: "danger"
            }
          ]
        });
      }

      if (lrContextParts.length > 0) {
        lrBlocks.push({
          type: "context",
          elements: [
            { type: "mrkdwn", text: lrContextParts.join(" | ") }
          ]
        });
      }

      webhook.payload = {
        username: AUTUMN_USERNAME,
        icon_url: AUTUMN_ICON_URL,
        text: lrPreview,
        blocks: lrBlocks
      };
      return webhook;
    }

    // ============ balances.usage_alert_triggered ============
    if (webhook.eventType === "balances.usage_alert_triggered") {
      var uaCustomerId = data.customer_id || "";
      var uaFeatureId = data.feature_id || "feature";
      var uaEntityId = data.entity_id || null;
      var uaAlert = data.usage_alert || {};
      var uaAlertName = uaAlert.name || "Usage alert";
      var uaThreshold = uaAlert.threshold;
      var uaThresholdType = uaAlert.threshold_type || "usage";

      function formatThreshold(value, type) {
        if (value === undefined || value === null) return "—";
        if (type === "usage_percentage") return value + "% used";
        if (type === "remaining_percentage") return value + "% remaining";
        if (type === "remaining") return value + " remaining";
        return value + " used";
      }

      var uaThresholdLabel = formatThreshold(uaThreshold, uaThresholdType);

      var uaCustomerLink = uaCustomerId
        ? "<" + AUTUMN_BASE + uaCustomerId + "|`" + uaCustomerId + "`>"
        : "`unknown`";

      var uaSentence =
        uaCustomerLink + " crossed the *" + uaAlertName + "* threshold on *" + uaFeatureId + "*";

      var uaPreview = "📊 Usage Alert: " + uaAlertName + " (" + uaCustomerId + ")";

      var uaFields = [
        { type: "mrkdwn", text: "*Customer:*\n" + uaCustomerLink },
        { type: "mrkdwn", text: "*Feature:*\n`" + uaFeatureId + "`" },
        { type: "mrkdwn", text: "*Alert:*\n" + uaAlertName },
        { type: "mrkdwn", text: "*Threshold:*\n" + uaThresholdLabel }
      ];
      if (uaEntityId) {
        uaFields.push({ type: "mrkdwn", text: "*Entity:*\n`" + uaEntityId + "`" });
      }

      var uaContextParts = [];
      if (uaCustomerId) uaContextParts.push("Customer ID: `" + uaCustomerId + "`");
      if (uaEntityId) uaContextParts.push("Entity: `" + uaEntityId + "`");

      var uaBlocks = [
        {
          type: "header",
          text: { type: "plain_text", text: "📊 Usage Alert: " + uaAlertName, emoji: true }
        },
        {
          type: "section",
          text: { type: "mrkdwn", text: uaSentence }
        },
        {
          type: "section",
          fields: uaFields
        }
      ];

      if (uaCustomerId) {
        uaBlocks.push({
          type: "actions",
          elements: [
            {
              type: "button",
              text: { type: "plain_text", text: "View in Autumn", emoji: true },
              url: AUTUMN_BASE + uaCustomerId
            }
          ]
        });
      }

      if (uaContextParts.length > 0) {
        uaBlocks.push({
          type: "context",
          elements: [
            { type: "mrkdwn", text: uaContextParts.join(" | ") }
          ]
        });
      }

      webhook.payload = {
        username: AUTUMN_USERNAME,
        icon_url: AUTUMN_ICON_URL,
        text: uaPreview,
        blocks: uaBlocks
      };
      return webhook;
    }

    // Cancel any other event types — they don't match Slack's expected schema and would error.
    webhook.cancel = true;
    return webhook;
  }
  ```

  {/* Source: others/svix-transforms/discord.ts */}

  ```js Discord theme={null}
  /**
   * @param webhook the webhook object
   * @param webhook.method destination method. Allowed values: "POST", "PUT"
   * @param webhook.url current destination address
   * @param webhook.eventType current webhook Event Type
   * @param webhook.payload JSON payload
   * @param webhook.cancel whether to cancel dispatch of the given webhook
   */
  function handler(webhook) {
    var AUTUMN_BASE = "https://app.useautumn.com/customers/";
    var AUTUMN_USERNAME = "Autumn";
    var AUTUMN_AVATAR_URL = "https://i.ibb.co/BHCF1ZqL/autumnicon.png";

    // Discord embed colors (decimal RGB).
    var COLOR_SUCCESS = 0x22c55e;
    var COLOR_INFO    = 0x3b82f6;
    var COLOR_WARNING = 0xf59e0b;
    var COLOR_DANGER  = 0xef4444;
    var COLOR_NEUTRAL = 0x6b7280;

    var payload = webhook.payload || {};
    var data = payload.data || payload;

    // ============ customer.products.updated ============
    if (webhook.eventType === "customer.products.updated") {
      var scenario = data.scenario || "updated";
      var customer = data.customer || {};
      var entity = data.entity || null;
      var product = data.updated_product || {};

      var customerName = customer.name || customer.email || customer.id || "Customer";
      var customerEmail = customer.email || null;
      var customerId = customer.id || "";

      var productName = product.name || "their plan";
      if (product.version && product.version !== 1) {
        productName = productName + " V" + product.version;
      }

      var entityLabel = null;
      if (entity) {
        entityLabel = entity.name || entity.id || null;
      }

      var emoji = "🔔";
      var header = "Subscription Updated";
      var verb = "updated";
      var color = COLOR_NEUTRAL;

      if (scenario === "new") {
        emoji = "🎉"; header = "New Subscription"; verb = "subscribed to"; color = COLOR_SUCCESS;
      } else if (scenario === "upgrade") {
        emoji = "🚀"; header = "Customer Upgraded"; verb = "upgraded to"; color = COLOR_SUCCESS;
      } else if (scenario === "downgrade") {
        emoji = "📉"; header = "Customer Downgraded"; verb = "downgraded to"; color = COLOR_WARNING;
      } else if (scenario === "cancel") {
        emoji = "⚠️"; header = "Subscription Cancelled"; verb = "cancelled"; color = COLOR_WARNING;
      } else if (scenario === "renew") {
        emoji = "🔄"; header = "Subscription Uncancelled"; verb = "uncancelled"; color = COLOR_SUCCESS;
      } else if (scenario === "expired") {
        emoji = "💀"; header = "Subscription Expired"; verb = "expired on"; color = COLOR_DANGER;
      } else if (scenario === "scheduled") {
        emoji = "📅"; header = "Change Scheduled"; verb = "scheduled a change to"; color = COLOR_INFO;
      }

      var sentence;
      if (scenario === "expired") {
        sentence = "**" + customerName + "**'s **" + productName + "** expired";
      } else {
        sentence = "**" + customerName + "** " + verb + " **" + productName + "**";
      }

      var description = sentence;
      if (customerId) {
        description += "\n\n[View in Autumn](" + AUTUMN_BASE + customerId + ")";
      }

      var fields = [
        { name: "Customer", value: customerName, inline: true }
      ];
      if (customerEmail) {
        fields.push({ name: "Email", value: customerEmail, inline: true });
      }
      fields.push({ name: "Product", value: productName, inline: true });
      fields.push({ name: "Scenario", value: "`" + scenario + "`", inline: true });
      if (entityLabel) {
        fields.push({ name: "Entity", value: entityLabel, inline: true });
      }

      var embed = {
        title: emoji + " " + header,
        description: description,
        color: color,
        fields: fields
      };
      if (customerId) {
        embed.url = AUTUMN_BASE + customerId;
      }
      var footerParts = [];
      if (customerId) footerParts.push("Customer ID: " + customerId);
      if (entity && entity.id) footerParts.push("Entity: " + entity.id);
      if (footerParts.length > 0) {
        embed.footer = { text: footerParts.join(" | ") };
      }

      webhook.payload = {
        username: AUTUMN_USERNAME,
        avatar_url: AUTUMN_AVATAR_URL,
        embeds: [embed]
      };
      return webhook;
    }

    // ============ balances.limit_reached ============
    if (webhook.eventType === "balances.limit_reached") {
      var lrCustomerId = data.customer_id || "";
      var lrFeatureId = data.feature_id || "feature";
      var lrLimitType = data.limit_type || "included";
      var lrEntityId = data.entity_id || null;

      var lrCustomerDisplay = lrCustomerId
        ? "[`" + lrCustomerId + "`](" + AUTUMN_BASE + lrCustomerId + ")"
        : "`unknown`";

      var lrDescription =
        lrCustomerDisplay + " hit their **" + lrFeatureId + "** `" + lrLimitType + "` limit";
      if (lrCustomerId) {
        lrDescription += "\n\n[View in Autumn](" + AUTUMN_BASE + lrCustomerId + ")";
      }

      var lrFields = [
        {
          name: "Customer",
          value: lrCustomerId ? "`" + lrCustomerId + "`" : "—",
          inline: true
        },
        { name: "Feature", value: "`" + lrFeatureId + "`", inline: true },
        { name: "Limit Type", value: "`" + lrLimitType + "`", inline: true }
      ];
      if (lrEntityId) {
        lrFields.push({ name: "Entity", value: "`" + lrEntityId + "`", inline: true });
      }

      var lrEmbed = {
        title: "🚫 Limit Reached",
        description: lrDescription,
        color: COLOR_DANGER,
        fields: lrFields
      };
      if (lrCustomerId) {
        lrEmbed.url = AUTUMN_BASE + lrCustomerId;
      }
      var lrFooterParts = [];
      if (lrCustomerId) lrFooterParts.push("Customer ID: " + lrCustomerId);
      if (lrEntityId) lrFooterParts.push("Entity: " + lrEntityId);
      if (lrFooterParts.length > 0) {
        lrEmbed.footer = { text: lrFooterParts.join(" | ") };
      }

      webhook.payload = {
        username: AUTUMN_USERNAME,
        avatar_url: AUTUMN_AVATAR_URL,
        embeds: [lrEmbed]
      };
      return webhook;
    }

    // ============ balances.usage_alert_triggered ============
    if (webhook.eventType === "balances.usage_alert_triggered") {
      var uaCustomerId = data.customer_id || "";
      var uaFeatureId = data.feature_id || "feature";
      var uaEntityId = data.entity_id || null;
      var uaAlert = data.usage_alert || {};
      var uaAlertName = uaAlert.name || "Usage alert";
      var uaThreshold = uaAlert.threshold;
      var uaThresholdType = uaAlert.threshold_type || "usage";

      var uaThresholdLabel = "—";
      if (uaThreshold !== undefined && uaThreshold !== null) {
        if (uaThresholdType === "usage_percentage") {
          uaThresholdLabel = uaThreshold + "% used";
        } else if (uaThresholdType === "remaining_percentage") {
          uaThresholdLabel = uaThreshold + "% remaining";
        } else if (uaThresholdType === "remaining") {
          uaThresholdLabel = uaThreshold + " remaining";
        } else {
          uaThresholdLabel = uaThreshold + " used";
        }
      }

      var uaCustomerDisplay = uaCustomerId
        ? "[`" + uaCustomerId + "`](" + AUTUMN_BASE + uaCustomerId + ")"
        : "`unknown`";

      var uaDescription =
        uaCustomerDisplay + " crossed the **" + uaAlertName + "** threshold on **" + uaFeatureId + "**";
      if (uaCustomerId) {
        uaDescription += "\n\n[View in Autumn](" + AUTUMN_BASE + uaCustomerId + ")";
      }

      var uaFields = [
        {
          name: "Customer",
          value: uaCustomerId ? "`" + uaCustomerId + "`" : "—",
          inline: true
        },
        { name: "Feature", value: "`" + uaFeatureId + "`", inline: true },
        { name: "Alert", value: uaAlertName, inline: true },
        { name: "Threshold", value: uaThresholdLabel, inline: true }
      ];
      if (uaEntityId) {
        uaFields.push({ name: "Entity", value: "`" + uaEntityId + "`", inline: true });
      }

      var uaEmbed = {
        title: "📊 Usage Alert: " + uaAlertName,
        description: uaDescription,
        color: COLOR_WARNING,
        fields: uaFields
      };
      if (uaCustomerId) {
        uaEmbed.url = AUTUMN_BASE + uaCustomerId;
      }
      var uaFooterParts = [];
      if (uaCustomerId) uaFooterParts.push("Customer ID: " + uaCustomerId);
      if (uaEntityId) uaFooterParts.push("Entity: " + uaEntityId);
      if (uaFooterParts.length > 0) {
        uaEmbed.footer = { text: uaFooterParts.join(" | ") };
      }

      webhook.payload = {
        username: AUTUMN_USERNAME,
        avatar_url: AUTUMN_AVATAR_URL,
        embeds: [uaEmbed]
      };
      return webhook;
    }

    // Unmatched event type — cancel dispatch.
    webhook.cancel = true;
    return webhook;
  }
  ```
</CodeGroup>
