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

# Receive Top.gg Vote Webhooks

> Top.gg vote webhooks on Square Cloud: receive vote events in Node.js or Python, verify the x-topgg-signature HMAC with your secret and send a test vote.

## Introduction

* This guide assumes you have an approved bot on [top.gg](https://top.gg/) and are using either Node.js or Python for your project.
* Next, you will need to create an account on Square Cloud, which can be done through the [sign up page](https://squarecloud.app/en/signup). You can use your email to create an account.
* Finally, you need to have an active plan on your account. Compare the [available plans](https://squarecloud.app/en/pricing) and choose one according to your needs.

## Setting up the environment

<Tabs>
  <Tab title="Node.js">
    1. Before you begin, make sure you have Node.js and npm installed on your system. If you don't have them yet, you can download them from the [official Node.js website](https://nodejs.org/).
    2. Start a new Node.js project and enable the `import` syntax with the following commands:

    ```bash Terminal theme={"system"}
    npm init -y
    npm pkg set type=module
    ```

    These commands create a package.json file in the current directory.

    3. Install Express:

    ```bash Terminal theme={"system"}
    npm install express
    ```
  </Tab>

  <Tab title="Python">
    1. Ensure you have Python and Pip (a package manager for Python) installed on your system. If not, you can download them from the [official Python website](https://www.python.org/) and the [official Pip website](https://pypi.org/).
    2. Install the `flask` and `waitress` libraries using pip:

    ```bash theme={"system"}
    pip install flask waitress
    ```
  </Tab>
</Tabs>

## Setting up the project

**1. Get your webhook secret:**

* Choose the subdomain your application will use on Square Cloud (the `SUBDOMAIN` field of your configuration file), for example `mysite`. Your webhook URL will then be `https://mysite.squareweb.app/topgg`.
* On Top.gg, open your project's dashboard and go to **Webhooks**.
* Paste the webhook URL and save. Top.gg then generates a webhook secret that starts with `whs_`: copy it and keep it private. Your code reads it from the `TOPGG_WEBHOOK_SECRET` environment variable.

<Info>Top.gg signs every request with the `x-topgg-signature` header, in the format `t=<timestamp>,v1=<signature>`, where the signature is an HMAC SHA-256 of `<timestamp>.<raw body>` made with your secret. The code below recomputes it and rejects any request that doesn't match, so nobody else can send fake votes to your URL. See the [Top.gg webhook documentation](https://docs.top.gg/webhooks/overview) for details.</Info>

<Note>This guide uses Top.gg v1 webhooks. The legacy v0 webhook, which sends a password in the `Authorization` header with a different payload, is not covered here.</Note>

**2. Implement the webhook listener:**

The following sections provide code examples for both JavaScript and Python:

<Tabs>
  <Tab title="Node.js">
    The example verifies the signature with Node's built-in `crypto` module, so no Top.gg package is needed. The `Webhook` class of `@top-gg/sdk` 4.0.0 is not used: in our tests, its listener answered valid deliveries with a timeout error, which makes Top.gg retry them.

    ```javascript index.js theme={"system"}
    import crypto from "node:crypto";
    import express from "express";

    const app = express();
    // The whs_ secret from your project's Webhooks page on Top.gg
    const SECRET = process.env.TOPGG_WEBHOOK_SECRET;

    function isValidSignature(rawBody, header = "") {
      // Header format: t=<unix timestamp>,v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">
      const parts = Object.fromEntries(header.split(",").map((item) => item.split("=")));
      const timestamp = Number(parts.t);
      if (!SECRET || !Buffer.isBuffer(rawBody) || !parts.v1 || !timestamp) return false;
      // Reject requests older than 5 minutes (replay protection)
      if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
      const expected = crypto.createHmac("sha256", SECRET).update(`${parts.t}.`).update(rawBody).digest();
      const received = Buffer.from(parts.v1, "hex");
      return received.length === expected.length && crypto.timingSafeEqual(received, expected);
    }

    // express.raw keeps the exact bytes Top.gg signed
    app.post("/topgg", express.raw({ type: "*/*" }), (req, res) => {
      if (!isValidSignature(req.body, req.get("x-topgg-signature"))) return res.sendStatus(401);

      const { type, data } = JSON.parse(req.body);
      // "vote.create" is a real vote, "webhook.test" comes from the Send Test button
      if (type === "vote.create" || type === "webhook.test") {
        console.log(`Vote received successfully! User ID: ${data.user.platform_id}`);
      }
      res.sendStatus(204);
    });

    // Square Cloud sets PORT=80 and routes your subdomain's traffic to it
    const port = Number(process.env.PORT) || 80;
    app.listen(port, "0.0.0.0", () => console.log(`Listening on port ${port}`));
    ```
  </Tab>

  <Tab title="Python">
    For Python, you can use the following code:

    ```python app.py theme={"system"}
    import hashlib
    import hmac
    import json
    import os
    import time

    from flask import Flask, abort, request
    from waitress import serve

    app = Flask(__name__)

    # The whs_ secret from your project's Webhooks page on Top.gg
    SECRET = os.environ.get("TOPGG_WEBHOOK_SECRET", "")


    def is_valid_signature(raw_body: bytes, header: str) -> bool:
        # Header format: t=<unix timestamp>,v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">
        try:
            parts = dict(item.split("=", 1) for item in header.split(","))
            timestamp = parts["t"]
            signature = parts["v1"]
            age = abs(time.time() - int(timestamp))
        except (KeyError, ValueError):
            return False
        if age > 300:  # reject requests older than 5 minutes (replay protection)
            return False
        message = timestamp.encode() + b"." + raw_body
        expected = hmac.new(SECRET.encode(), message, hashlib.sha256).hexdigest()
        return hmac.compare_digest(expected.encode(), signature.encode())


    # Top.gg sends signed POST requests to this route
    @app.route("/topgg", methods=["POST"])
    def topgg_webhook():
        raw_body = request.get_data()
        if not SECRET or not is_valid_signature(raw_body, request.headers.get("x-topgg-signature", "")):
            abort(401)

        payload = json.loads(raw_body)
        # "vote.create" is a real vote, "webhook.test" comes from the Send Test button
        if payload["type"] in ("vote.create", "webhook.test"):
            print(f'Vote received successfully! User ID: {payload["data"]["user"]["platform_id"]}')
        return "", 204


    if __name__ == "__main__":
        # Square Cloud sets PORT=80 and routes your subdomain's traffic to it
        serve(app, host="0.0.0.0", port=int(os.environ.get("PORT", 80)))
    ```
  </Tab>
</Tabs>

## Creating the squarecloud config file

Create a `squarecloud.app` file in your project folder. Set `SUBDOMAIN` to the subdomain you chose in step 1:

<CodeGroup>
  ```systemd Node.js theme={"system"}
  DISPLAY_NAME=Top.gg Webhook
  MAIN=index.js
  MEMORY=512
  VERSION=recommended
  SUBDOMAIN=mysite
  ```

  ```systemd Python theme={"system"}
  DISPLAY_NAME=Top.gg Webhook
  MAIN=app.py
  MEMORY=512
  VERSION=recommended
  SUBDOMAIN=mysite
  ```
</CodeGroup>

`SUBDOMAIN` publishes the listener at `https://mysite.squareweb.app`, and web applications need at least `MEMORY=512`. The code listens on the port in the `PORT` environment variable, which Square Cloud sets to `80`, and on host `0.0.0.0`.

<Card title="Learn about: how to make the configuration file for Square Cloud." icon="link" href="/en/getting-started/config-file">
  The squarecloud.app file is a configuration file that will be used to configure your application; it will be used to define the name, description, version, main file, among other things.
</Card>

## Uploading your application to Square Cloud

After following all the steps, your project folder should contain your code, its dependency file (`package.json` or `requirements.txt`) and the configuration file. To upload from the dashboard, put them into a `.zip` file.

If your application is a Node.js project, take a look at our article on [Node.js](/en/articles/getting-started-with-nodejs).

If your application is a Python project, take a look at our article on [Python](/en/articles/getting-started-with-python).

### Via dashboard

<Steps>
  <Step title="Access the Upload Page">
    Access the [upload page](https://squarecloud.app/en/dashboard/new) and upload your project zip file.
  </Step>

  <Step title="Configure Your Environment">
    After uploading your zip, you will need to configure the name, main file or runtime environment and other settings for your project.\
    If you are uploading a web project, make sure to select "Web Publication" and set a subdomain to your project.
  </Step>

  <Step title="Deploy Your Project">
    Finally, click on the "Deploy" button to host your project on Square Cloud.\
    After deployment, you can monitor your project's status and logs from the dashboard.

    <Frame>
      <img src="https://cdn.squarecloud.app/docs/articles/dashboard/uploading.gif" alt="Uploading application to Square Cloud" style={{ borderRadius: "0.2rem" }} />
    </Frame>
  </Step>

  <Step title="Confirm Your App Is Live">
    Your first deploy usually takes less than a minute. In the [dashboard](https://squarecloud.app/en/dashboard), wait for your application status to show as running and check the logs for any startup errors.\
    If you deployed a website or API, open `https://<your-subdomain>.squareweb.app` in your browser: you should see your application responding. If you deployed a bot, send it a command to confirm it is online.
    <Tip>App not starting? See the [Troubleshooting guide](/en/platform/troubleshooting/app-wont-start) for the most common causes and fixes.</Tip>
  </Step>
</Steps>

### Via CLI

To use this method, your project needs a configuration file named `squarecloud.app` at its root. It tells Square Cloud how to run your application.

<Card title="Configuration file guide" icon="link" href="/en/getting-started/config-file">
  Learn how to create the `squarecloud.app` configuration file that defines your application's environment.
</Card>

<Steps>
  <Step title="Install the CLI">
    Install the [Square Cloud CLI](/en/cli-reference/installation). If you already have it, run the same command to update it:

    <CodeGroup>
      ```bash macOS, Linux, and WSL theme={"system"}
      curl -fsSL https://cli.squarecloud.app/install | bash
      ```

      ```bash Windows with npm theme={"system"}
      npm install -g @squarecloud/cli@latest
      ```
    </CodeGroup>
  </Step>

  <Step title="Log In">
    Run the command below. It opens your browser: approve the login there and the CLI is ready. There is no API key to copy. For scripts and CI, see [CLI authentication](/en/cli-reference/authentication).

    ```bash theme={"system"}
    squarecloud auth login
    ```
  </Step>

  <Step title="Upload Your Project">
    From your project folder, run the command below. The CLI zips the current folder, leaving out what [`squarecloud.ignore`](/en/getting-started/squarecloud-ignore) lists, and uploads it:

    ```bash theme={"system"}
    squarecloud upload
    ```

    To upload a zip you created yourself, pass it with `--file`:

    ```bash theme={"system"}
    squarecloud upload --file <path/to/zip>
    ```
  </Step>

  <Step title="Confirm Your App Is Live">
    Your first deploy usually takes less than a minute. Check your application status and logs directly from the terminal:

    ```bash theme={"system"}
    squarecloud app status
    squarecloud app logs
    ```

    If you deployed a website or API, open `https://<your-subdomain>.squareweb.app` in your browser: you should see your application responding. If you deployed a bot, send it a command to confirm it is online.
    <Tip>App not starting? See the [Troubleshooting guide](/en/platform/troubleshooting/app-wont-start) for the most common causes and fixes.</Tip>
  </Step>
</Steps>

## Setting the webhook secret

Add your `whs_` secret as the `TOPGG_WEBHOOK_SECRET` environment variable of your application, in one of these ways:

* **Dashboard**: on the upload page, open **Advanced configuration** and add the variable before you deploy. For an application that is already deployed, add it on its **Environment Variables** page.
* **CLI**: from your project folder, set it with [`squarecloud app env set`](/en/cli-reference/environment-variables#squarecloud-app-env-set) and restart the application:

```bash theme={"system"}
squarecloud app env set TOPGG_WEBHOOK_SECRET=whs_your_secret
squarecloud app restart
```

The application reads environment variables when it starts, so restart it after every change. Until the secret is set, the code rejects every request with `401 Unauthorized`. See [Environment variables](/en/platform/environment-variables) for the other ways to manage them.

## Starting tests

If you have done everything correctly, open `https://mysite.squareweb.app/topgg` in your browser (replace `mysite` with your subdomain). If "Cannot GET /topgg" (Node.js) or "Method Not Allowed" (Python) appears, everything is okay: the route only accepts the `POST` requests Top.gg sends.

* For the JavaScript code we created with `app.post("/topgg", ...)`, the route that will receive the votes is "/topgg". So, if your website is `mysite.squareweb.app`, the Webhook URL is `https://mysite.squareweb.app/topgg`.

* For the Python code we created with `@app.route("/topgg", methods=["POST"])`, the route that will receive the votes is also "/topgg". So, the Webhook URL is the same `https://mysite.squareweb.app/topgg`.

Finally, go back to your project's **Webhooks** page on Top.gg and click on the "Send Test" button. After that, check your application's logs. If everything went well, the message you defined in `console.log` or `print` should appear in the logs.

<Frame>
  <img src="https://cdn.squarecloud.app/docs/articles/topgg/example-send.png" alt="Top.gg webhook send test example" style={{ borderRadius: "0.2rem" }} />
</Frame>

And with that, if everything has been configured correctly, your webhook will be ready to send notifications when your bot receives a vote on top.gg.

## Troubleshooting

<CardGroup cols={1}>
  ### Custom domain

  <Card horizontal>
    To use a custom domain (e.g., `mysite.com`) instead of the default URL `mysite.squareweb.app`, you need the **Standard plan or higher**. The default URL comes from the **SUBDOMAIN** field in the configuration file. To connect your domain, follow [how to set up your own domain](https://help.squarecloud.app/en-us/article/how-to-set-up-your-own-domain-cnamedns-1fzbrgh/).
  </Card>

  ### Minimum RAM requirements

  <Card horizontal>
    **Minimum: 512MB RAM** for websites and APIs, enough for a static build on the static runtime. For apps that render pages on a server (Next.js, Nuxt, Angular SSR and similar), we recommend **at least 1GB RAM**. For larger applications, allocate more RAM to prevent the application from running out of memory and crashing.
  </Card>

  ### Could not find this site.

  <Card horizontal>
    Check if the **subdomain/domain** matches what's configured in the **SUBDOMAIN** field or in the **custom domain settings**. If you just uploaded the site, wait up to **60 seconds** for Square to enable **first access**.
  </Card>

  ### Site took too long to respond...

  <Card horizontal>
    Your server must listen on **port 80** and **host 0.0.0.0**. Square Cloud sets the **PORT** (`80`) and **HOST** (`0.0.0.0`) environment variables in your application: read them in your code instead of hardcoding other values. A server that listens only on `localhost` or `127.0.0.1` never receives requests.
  </Card>
</CardGroup>

## Next steps

<CardGroup cols={3}>
  <Card title="Discord bot" icon="discord" href="/en/tutorials/bots/discord">
    Host the bot that receives the votes.
  </Card>

  <Card title="Environment variables" icon="key" href="/en/platform/environment-variables">
    Manage your webhook secret and other secrets.
  </Card>

  <Card title="App won't start" icon="triangle-exclamation" href="/en/platform/troubleshooting/app-wont-start">
    Fix port conflicts, missing modules and other startup failures.
  </Card>
</CardGroup>

## Contact us

If you continue facing **technical difficulties**, our **specialized support team** is available to assist you. [**Contact us**](https://squarecloud.app/en/support) and we'll be happy to help you resolve any issue: support quality is a big part of why developers rate Square Cloud [**4.9/5 across 402 reviews**](https://www.trustpilot.com/review/squarecloud.app) on Google and Trustpilot.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.