A discord bot that will notify you about new orders, new subscriptions, and more!
  • JavaScript 100%
Find a file
2026-08-20 01:26:29 -05:00
src Added Balance Milestones 2026-08-20 01:26:29 -05:00
.env.example Added Balance Milestones 2026-08-20 01:26:29 -05:00
.gitignore Added Balance Milestones 2026-08-20 01:26:29 -05:00
LICENSE Added ReadMe.MD and License 2026-08-13 20:17:47 -05:00
package-lock.json yes 2026-08-06 20:21:33 -05:00
package.json yes 2026-08-06 20:21:33 -05:00
README.MD Added Balance Milestones 2026-08-20 01:26:29 -05:00

FW-Notifier

Fourthwall → Discord notifications, made simple.

A lightweight Node.js service that receives Fourthwall webhook events and delivers them to a Discord channel as rich embeds.

Node.js Discord.js Express MIT License


Overview

FW-Notifier bridges Fourthwall and Discord.

When a supported Fourthwall event occurs, Fourthwall sends a webhook to FW-Notifier. The service processes the event, generates a Discord embed, and sends the notification directly to your configured Discord channel.

┌──────────────┐
│   Fourthwall │
└──────┬───────┘
       │
       │ Webhook
       ▼
┌────────────────┐
│   FW-Notifier  │
│                │
│  Express API   │
│       +        │
│ Event Handlers │
└───────┬────────┘
        │
        │ Discord Embed
        ▼
┌────────────────┐
│    Discord     │
│                │
│  Notification  │
│    Channel     │
└────────────────┘

🚀 Features

  • 🔔 Fourthwall Webhooks — Receive Fourthwall events through a simple HTTP endpoint.
  • 💬 Discord Notifications — Send events directly to a Discord channel.
  • 🎨 Rich Embeds — Event handlers can turn webhook data into formatted Discord embeds.
  • 🔐 Webhook Authentication — Optional X-Webhook-Secret authentication.
  • Lightweight — Built with Node.js and Express with minimal overhead.
  • 🩺 Health Endpoint — Built-in /health endpoint for monitoring.
  • ⚙️ Environment Configuration — Configure the application entirely through .env.
  • 🏷️ Custom Branding — Configure the bot name, color, icon, status, and activity.
  • 🧩 Extensible Handlers — Add support for additional Fourthwall event types through event handlers.

📦 Requirements

Before installing FW-Notifier, make sure you have:

  • Node.js installed.
  • A Discord bot.
  • The Discord bot token.
  • The ID of the Discord channel that should receive notifications.
  • A publicly accessible server if Fourthwall needs to reach your webhook.
  • A Fourthwall webhook configuration.

🛠️ Installation

1. Clone the repository

git clone https://git.exoworo.com/exoworo/FW-Notifier.git
cd FW-Notifier

2. Install dependencies

npm install

3. Configure environment variables

Copy the example environment file:

cp .env.example .env

Then edit .env:

DISCORD_TOKEN=your_bot_token_here
DISCORD_CHANNEL_ID=your_channel_id_here
BALANCE_DISCORD_CHANNEL_ID=your_balance_channel_id_here

PORT=9674
WEBHOOK_SLUG=/api/v1.0/fourthwall
WEBHOOK_SECRET=your_webhook_secret_here

BRAND_NAME=FW-Notifier
BRAND_COLOR=#5865F2
BRAND_ICON_URL=

BOT_STATUS=online
BOT_ACTIVITY_TYPE=Watching
BOT_ACTIVITY_TEXT=Fourthwall Orders

FOURTHWALL_API_BASE_URL=https://api.fourthwall.com
FOURTHWALL_BALANCE_PATH=/open-api/v1.0/samples/balance
FOURTHWALL_API_EMAIL=your_fourthwall_email_here
FOURTHWALL_API_PASSWORD=your_fourthwall_password_here
BALANCE_POLL_INTERVAL_MS=900000
BALANCE_STATE_FILE=.balance-state.json

⚠️ Never commit your .env file or expose your Discord bot token.

4. Start the application

node index.js

If everything is configured correctly, FW-Notifier will log into Discord and start the webhook server.

You should see output similar to:

Webhook server listening on port 9674
Endpoint: POST /api/v1.0/fourthwall
Endpoint: POST /api/v1.0/fourthwall/:type

⚙️ Configuration

FW-Notifier is configured through environment variables.

Variable Default Description
DISCORD_TOKEN Discord bot token.
DISCORD_CHANNEL_ID Discord channel where notifications are sent.
BALANCE_DISCORD_CHANNEL_ID Separate Discord channel for balance milestones.
FOURTHWALL_API_BASE_URL https://api.fourthwall.com Fourthwall API base URL.
FOURTHWALL_BALANCE_PATH /open-api/v1.0/samples/balance Balance API path.
FOURTHWALL_API_EMAIL Fourthwall Basic Auth username/email.
FOURTHWALL_API_PASSWORD Fourthwall Basic Auth password.
BALANCE_POLL_INTERVAL_MS 900000 Balance polling interval in milliseconds (15 minutes).
BALANCE_STATE_FILE .balance-state.json Local file used to prevent duplicate milestone posts.

Balance milestones

When configured, the service polls Fourthwall's documented GET /open-api/v1.0/samples/balance endpoint and sends milestone embeds to BALANCE_DISCORD_CHANNEL_ID. It celebrates 500, 1000, 1500, and 2000, then continues in 1000 increments (3000, 4000, and so on). The state file prevents the same upward crossing from being announced repeatedly after a restart. | PORT | 9674 | Port used by the Express server. | | WEBHOOK_SLUG | /api/v1.0/fourthwall | Base webhook URL path. | | WEBHOOK_SECRET | — | Optional secret used to authenticate webhook requests. | | BRAND_NAME | FW-Notifier | Name displayed by the notification system. | | BRAND_COLOR | #5865F2 | Discord embed color. | | BRAND_ICON_URL | — | Optional branding/icon URL. | | BOT_STATUS | online | Discord bot status. | | BOT_ACTIVITY_TYPE | Watching | Discord activity type. | | BOT_ACTIVITY_TEXT | Fourthwall Orders | Discord activity text. |


🔗 Webhook API

Base Endpoint

POST /api/v1.0/fourthwall

The event type is provided in the request body.

Example:

{
  "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
  "webhookId": "00aa4abd-5778-4199-8161-0b49b2f212e5",
  "shopId": "sh_c689d374-22ca-43d3-8d29-9ef0805cc4cb",
  "type": "ORDER_PLACED",
  "apiVersion": "V1",
  "createdAt": "2020-08-13T09:05:36.939+00:00",
  "testMode": false,
  "data": {
    "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
    "shopId": "sh_c689d374-22ca-43d3-8d29-9ef0805cc4cb",
    "friendlyId": "D3XZFWPP",
    "checkoutId": "ch_BV44UYrXQA2T_Xcf1288tw",
    "email": "supporter@fourthwall.com",
    "emailMarketingOptIn": true,
    "message": "Sample message",
    "amounts": {
      "subtotal": {
        "value": 10,
        "currency": "USD"
      },
      "shipping": {
        "value": 10,
        "currency": "USD"
      },
      "tax": {
        "value": 10,
        "currency": "USD"
      },
      "donation": {
        "value": 10,
        "currency": "USD"
      },
      "discount": {
        "value": 10,
        "currency": "USD"
      },
      "giftCards": [
        {
          "code": "<string>",
          "amountUsed": {
            "value": 10,
            "currency": "USD"
          }
        }
      ],
      "total": {
        "value": 10,
        "currency": "USD"
      }
    },
    "offers": [
      {
        "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
        "name": "My t-shirt",
        "slug": "my-t-shirt",
        "description": "My t-shirt description",
        "variant": {
          "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
          "name": "My t-shirt - Black, L",
          "sku": "WDEK-DRE200L",
          "unitPrice": {
            "value": 10,
            "currency": "USD"
          },
          "attributes": {
            "description": "Black, L"
          },
          "quantity": 5,
          "price": {
            "value": 10,
            "currency": "USD"
          },
          "weight": {
            "value": 1,
            "unit": "kg"
          },
          "dimensions": {
            "length": 1,
            "width": 2,
            "height": 3,
            "unit": "cm"
          }
        }
      }
    ],
    "source": {
      "type": "<string>",
      "giftId": "gft_EdJvIXu3SEiXe_QkPavHSA"
    },
    "createdAt": "2020-08-13T09:05:36.939Z",
    "updatedAt": "2020-08-13T09:05:36.939Z",
    "promotionId": "prm_EdJvIXu3SEiXe_QkPavHSA",
    "username": "Johnny123",
    "trackingParams": {
      "fbc": "<string>",
      "fbp": "<string>",
      "utm_source": "<string>",
      "utm_medium": "<string>",
      "utm_campaign": "<string>",
      "utm_content": "<string>",
      "utm_term": "<string>"
    },
    "metadata": {}
  }
}

Data from Fourthwall Api Documentation

This is useful when your webhook provider allows you to configure a separate endpoint for each event type.

FW-Notifier extracts type and passes data to the corresponding event handler.

Event-Specific Endpoint

You can also provide the event type directly in the URL:

POST /api/v1.0/fourthwall/ORDER_PLACED

With a request body such as:

{
  "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
  "webhookId": "00aa4abd-5778-4199-8161-0b49b2f212e5",
  "shopId": "sh_c689d374-22ca-43d3-8d29-9ef0805cc4cb",
  "type": "ORDER_PLACED",
  "apiVersion": "V1",
  "createdAt": "2020-08-13T09:05:36.939+00:00",
  "testMode": false,
  "data": {
    "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
    "shopId": "sh_c689d374-22ca-43d3-8d29-9ef0805cc4cb",
    "friendlyId": "D3XZFWPP",
    "checkoutId": "ch_BV44UYrXQA2T_Xcf1288tw",
    "email": "supporter@fourthwall.com",
    "emailMarketingOptIn": true,
    "message": "Sample message",
    "amounts": {
      "subtotal": {
        "value": 10,
        "currency": "USD"
      },
      "shipping": {
        "value": 10,
        "currency": "USD"
      },
      "tax": {
        "value": 10,
        "currency": "USD"
      },
      "donation": {
        "value": 10,
        "currency": "USD"
      },
      "discount": {
        "value": 10,
        "currency": "USD"
      },
      "giftCards": [
        {
          "code": "<string>",
          "amountUsed": {
            "value": 10,
            "currency": "USD"
          }
        }
      ],
      "total": {
        "value": 10,
        "currency": "USD"
      }
    },
    "offers": [
      {
        "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
        "name": "My t-shirt",
        "slug": "my-t-shirt",
        "description": "My t-shirt description",
        "variant": {
          "id": "00aa4abd-5778-4199-8161-0b49b2f212e5",
          "name": "My t-shirt - Black, L",
          "sku": "WDEK-DRE200L",
          "unitPrice": {
            "value": 10,
            "currency": "USD"
          },
          "attributes": {
            "description": "Black, L"
          },
          "quantity": 5,
          "price": {
            "value": 10,
            "currency": "USD"
          },
          "weight": {
            "value": 1,
            "unit": "kg"
          },
          "dimensions": {
            "length": 1,
            "width": 2,
            "height": 3,
            "unit": "cm"
          }
        }
      }
    ],
    "source": {
      "type": "<string>",
      "giftId": "gft_EdJvIXu3SEiXe_QkPavHSA"
    },
    "createdAt": "2020-08-13T09:05:36.939Z",
    "updatedAt": "2020-08-13T09:05:36.939Z",
    "promotionId": "prm_EdJvIXu3SEiXe_QkPavHSA",
    "username": "Johnny123",
    "trackingParams": {
      "fbc": "<string>",
      "fbp": "<string>",
      "utm_source": "<string>",
      "utm_medium": "<string>",
      "utm_campaign": "<string>",
      "utm_content": "<string>",
      "utm_term": "<string>"
    },
    "metadata": {}
  }
}

Data from Fourthwall Api Documentation

This is useful when your webhook provider allows you to configure a separate endpoint for each event type.


🔐 Webhook Security

FW-Notifier supports optional webhook authentication through the X-Webhook-Secret header.

Set a secret in .env:

WEBHOOK_SECRET=your_webhook_secret

Then include the same value when making webhook requests:

X-Webhook-Secret: your_webhook_secret

Requests with an invalid secret receive 401 Unauthorized.

{
  "error": "Unauthorized"
}

If WEBHOOK_SECRET is empty, webhook authentication is disabled.

💡 Recommended: Always configure a webhook secret when exposing FW-Notifier to the public internet.


🩺 Health Check

FW-Notifier includes a simple health endpoint:

GET /health

A healthy server responds with:

{
  "status": "ok"
}

This can be used with uptime monitoring, reverse proxies, Docker health checks, hosting platforms, load balancers, or custom monitoring systems.


📡 Response Codes

Status Description
200 Request processed successfully.
400 Unsupported event type.
401 Invalid webhook secret.
503 Discord channel is not ready.
500 Internal processing error.

Successful webhook requests return:

{
  "status": "ok"
}

Unsupported events return:

{
  "error": "Unsupported type: EVENT_TYPE"
}

🧩 Event Handlers

FW-Notifier uses an event-handler system to process different Fourthwall events.

Fourthwall Event
       │
       ▼
   Event Type
       │
       ▼
 handlers[type]
       │
       ▼
 Discord Embed
       │
       ▼
 Discord Channel

This makes it easy to add support for additional event types without changing the webhook server itself.

For example, an ORDER_PLACED event can be handled by an ORDER_PLACED handler which receives the event data and returns a Discord embed.


🤖 Discord Bot Setup

Create a Discord application and bot, then add the bot to your server.

The bot needs access to the configured notification channel.

At minimum, make sure the bot can:

  • View the channel
  • Send messages
  • Embed links

You will need the bot token for:

DISCORD_TOKEN=your_bot_token_here

And the target channel ID for:

DISCORD_CHANNEL_ID=your_channel_id_here

🌐 Fourthwall Setup

Once FW-Notifier is running and publicly accessible, configure Fourthwall to send its webhook events to:

https://your-domain.com/api/v1.0/fourthwall

If you are using the event-specific format:

https://your-domain.com/api/v1.0/fourthwall/ORDER_PLACED

If webhook authentication is enabled, make sure your Fourthwall webhook configuration sends the matching secret using:

X-Webhook-Secret: your_webhook_secret

Note: Your server must be publicly reachable by Fourthwall for webhook delivery to work.


🏗️ Architecture

FW-Notifier keeps the HTTP layer and Discord logic separated.

Startup

index.js
   │
   ├── Login to Discord
   │
   └── Create Express Server
             │
             ├── Webhook Routes
             └── Health Route

Webhook Processing

Incoming Request
       │
       ▼
Secret Validation
       │
       ▼
Event Type Lookup
       │
       ▼
Event Handler
       │
       ▼
Discord Embed
       │
       ▼
Configured Channel

🛡️ Production Recommendations

If you're running FW-Notifier in production, consider:

  • Use HTTPS.
  • Configure WEBHOOK_SECRET.
  • Never commit .env.
  • Keep your Discord token private.
  • Run the application with a process manager such as PM2.
  • Put the Express server behind a reverse proxy when appropriate.
  • Restrict your Discord bot's permissions to only what it needs.
  • Monitor /health.
  • Keep Node.js and project dependencies updated.

Example with PM2

Install PM2:

npm install -g pm2

Start FW-Notifier:

pm2 start npm -- name "fw-notifier" -- start

Save the process:

pm2 save

🐛 Troubleshooting

Discord bot does not connect

Check:

DISCORD_TOKEN=your_bot_token_here

Make sure the token is valid and the bot has been added to your Discord server.

No notifications appear

Verify:

  • DISCORD_CHANNEL_ID is correct.
  • The bot can access the channel.
  • The bot can send messages.
  • The bot can embed links.
  • The Discord client successfully connected.
  • The event type is supported by handlers.js.

401 Unauthorized

Your webhook secret does not match.

Check:

WEBHOOK_SECRET=your_webhook_secret

and make sure the incoming request contains the matching X-Webhook-Secret header.

400 Unsupported type

The received event type does not have a matching handler.

Check the event type and the available handlers in handlers.js.

503 Channel not ready

The Discord target channel could not be accessed at the time of the request.

Check:

  • Discord connection status.
  • DISCORD_CHANNEL_ID.
  • Bot permissions.
  • Discord availability.

500 Internal error

An error occurred while processing the event.

Check the FW-Notifier console output for the underlying error.


🔒 Security

Please keep the following secrets private:

DISCORD_TOKEN=
WEBHOOK_SECRET=

Do not:

  • Commit .env to Git.
  • Paste your bot token into public issues.
  • Share your webhook secret publicly.
  • Hard-code secrets into source files.

A .gitignore should include:

.env
node_modules/

📄 License

FW-Notifier is licensed under the MIT License.

MIT License

Copyright (c) 2026 Exoworo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

FW-Notifier
Fourthwall notifications delivered directly to Discord.

Made with ❤️ by Exoworo