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

# Bot Vote Webhooks

> Receive real-time vote events for bot listings and react in your own backend.

Use this guide when you want Rankly to push vote events to your bot or backend.

## What the webhook contains

Rankly sends a signed JSON payload that looks like this:

```json theme={null}
{
  "event": "bot_vote",
  "timestamp": "2026-01-01T12:00:00.000Z",
  "voter": {
    "userId": "123456789012345678",
    "username": "Skyline"
  },
  "bot": {
    "id": "987654321098765432",
    "name": "Rankly Bot"
  },
  "creditsAwarded": true,
  "adBlocker": false,
  "creditDenialReason": null
}
```

The signature is sent in the `X-Webhook-Signature` header.

## How to handle it

<Steps>
  <Step title="Expose a public HTTPS endpoint">
    The endpoint must be reachable from the public internet.
  </Step>

  <Step title="Verify the signature">
    Recompute the HMAC with your webhook secret and compare it to the `X-Webhook-Signature` header.
  </Step>

  <Step title="Deduplicate events">
    Treat the webhook as at-least-once delivery and ignore duplicates by storing a stable event fingerprint.
  </Step>

  <Step title="Respond quickly">
    Return a 2xx response before you do slow work like database updates or role grants.
  </Step>
</Steps>

## Example verification

```javascript theme={null}
import crypto from 'crypto';
import express from 'express';

const app = express();
app.use(express.json({
  verify: (req, _res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
}));

app.post('/rankly/bot-vote', async (req, res) => {
  const receivedSignature = req.header('X-Webhook-Signature');
  const expectedSignature = crypto
    .createHmac('sha256', process.env.RANKLY_WEBHOOK_SECRET)
    .update(req.rawBody)
    .digest('hex');

  if (receivedSignature !== expectedSignature) {
    return res.status(401).json({ error: 'invalid signature' });
  }

  const { voter, bot, creditsAwarded, creditDenialReason } = req.body;

  // Grant rewards, log the vote, or update your internal state here.
  console.log({ voter, bot, creditsAwarded, creditDenialReason });

  return res.status(200).json({ received: true });
});
```

## What to build with it

* Reward a voter inside your bot
* Log the vote in a moderation channel
* Update a dashboard badge or vote count
* Trigger a lightweight analytics event

## Security notes

* Keep the secret private and rotate it if it leaks.
* Verify the exact payload you received, not a re-serialized version that may change formatting.
* Use a unique event key to avoid double rewards.
* Do not expose your webhook endpoint with destructive actions on the first request.

## Related docs

<CardGroup cols={2}>
  <Card title="Bot API Integration" href="/bots/api-integration">
    Read data on demand when a webhook is not necessary.
  </Card>

  <Card title="Public API Reference" href="/servers/api-reference">
    Pull server and vote state directly from Rankly.
  </Card>
</CardGroup>
