Skip to content

Events (webhooks)

With events, Catita notifies your bot when something happens in the server, so it doesn’t have to keep asking.

In /settings > API > Add webhook, enter your bot’s URL (it must be https and public). Catita shows you the signing secret (whsec_…) once: save it. Up to 3 webhooks per server.

On the webhook’s screen you pick the events, test it with Test (sends a ping), change the secret or turn it off.

Event When data
level.up Someone levels up userId, instance, level, oldLevel
level.rebirth Someone rebirths userId, instance, rebirths, level
streak.updated Someone extends their streak userId, instance, current, best
customrole.key_redeemed Someone redeems a custom role key userId, key, days
economy.wallet_changed The API changes a wallet userId, instance, amount, wallet, keyId

A JSON POST:

{
"id": "6f1c…",
"type": "level.up",
"guildId": "111111111111111111",
"createdAt": "2026-11-04T18:00:00.000Z",
"data": { "userId": "123456789012345678", "instance": "1", "level": 12, "oldLevel": 11 }
}

With these headers:

  • Catita-Event: the type.
  • Catita-Delivery: the event id (if it arrives twice, ignore the repeat).
  • Catita-Timestamp: Unix seconds of the delivery.
  • Catita-Signature: v1= plus the hex HMAC-SHA256 of <timestamp>.<body> with your secret.

Answer with a 2xx within 5 seconds. If it fails, Catita retries 5 more times with growing waits (10 s, 20 s, 40 s…). After 25 failures in a row it turns the webhook off and posts in the log channel. Redirects are not followed.

Always check the signature and that the timestamp is recent, so you know the notification comes from Catita:

const crypto = require('node:crypto');
const express = require('express');
const app = express();
app.post('/catita', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('Catita-Timestamp');
const given = req.get('Catita-Signature') || '';
const expected = 'v1=' + crypto
.createHmac('sha256', process.env.CATITA_WEBHOOK_SECRET)
.update(`${timestamp}.${req.body}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
const valid = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!fresh || !valid) return res.sendStatus(401);
const event = JSON.parse(req.body);
if (event.type === 'level.up') {
console.log(`${event.data.userId} reached level ${event.data.level}`);
}
res.sendStatus(204);
});
app.listen(3000);

Use the body exactly as it arrives (don’t re-serialize it), or the signature won’t match.

Catita is a project by Random Dev, a development studio owned by ZENU GROUP LLC. catita.gg