falafy
EN
Download free

Public API v1 · @falafy/bot

Interactions: commands, components and modals

When someone uses a command, clicks a button or submits a modal, the app gets an interaction and has 3 seconds to answer.

Register commands

Global commands work in every server the bot is in; server commands work only there and instantly (great for testing). PUT replaces the whole list of that scope. Types: 1 chat (/name), 2 user menu, 3 message menu. Options: 3 string, 4 integer, 5 boolean, 6 user, 7 channel, 8 role, 10 number; with choices (up to 25), minValue/maxValue, minLength/maxLength or autocomplete. Limits: 100 chat, 15 user and 15 message commands per scope.

PUT /api/v1/applications/APP_ID/commands
[
  { "name": "dado", "description": "Rola um dado",
    "options": [{ "type": 4, "name": "lados", "description": "Quantos lados", "minValue": 2, "maxValue": 100 }] },
  { "name": "jogo", "description": "Escolhe um jogo",
    "options": [{ "type": 3, "name": "nome", "description": "Nome", "autocomplete": true }] },
  { "name": "kick", "description": "Só admins", "defaultMemberPermissions": "0" },
  { "type": 2, "name": "Ver perfil" }
]

defaultMemberPermissions hides the command from people without those permissions ("0" = administrators only).

The interaction

The gateway delivers INTERACTION_CREATE with id, token, type (2 command, 3 component, 4 autocomplete, 5 modal), user, member (roles and permissions), channelId, serverId and data with already-typed options.

Respond

POST /api/v1/interactions/{id}/{token}/callback, with no Authorization (the token is the credential), exactly once and within 3 seconds:

  • 4 message (with flags: 64 it is ephemeral: only the invoker sees it, and it never enters channel history).
  • 5 deferred: “thinking…” shows up; edit it later with PATCH /webhooks/{appId}/{token}/messages/@original.
  • 6 acknowledges a click without changes · 7 edits the component’s message.
  • 8 autocomplete choices (up to 25) · 9 opens a modal.

After the initial response the token stays valid for 15 minutes for follow-ups (POST /webhooks/{appId}/{token}) and to edit or delete those messages. If the app doesn’t answer in time, the person sees “The interaction failed”.

Buttons, menus and modals

Rows (type 1) hold up to 5 buttons or 1 select menu. Buttons: 1 primary, 2 secondary, 3 success, 4 danger (all with customId) and 5 link (with url, no interaction). String selects: up to 25 options, minValues/maxValues. Modals: a title and up to 5 text fields (short or paragraph), one per row.

Receive over HTTP

Save an interactions URL in the portal and interactions arrive as POST requests to your server, signed with Ed25519: verify X-Signature-Ed25519 over X-Signature-Timestamp + body with the app’s public key and answer 401 when it doesn’t match. When you save, Falafy sends a PING (answer {"type":1}) and a request with a wrong signature (it must get 401). The response can come in the HTTP response body itself.

import crypto from 'node:crypto';
import http from 'node:http';

const PUBLIC_KEY = process.env.FALAFY_PUBLIC_KEY; // hex, no portal
const spki = Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), Buffer.from(PUBLIC_KEY, 'hex')]);
const key = crypto.createPublicKey({ key: spki, format: 'der', type: 'spki' });

http.createServer((req, res) => {
  let raw = '';
  req.on('data', (c) => (raw += c));
  req.on('end', () => {
    const sig = req.headers['x-signature-ed25519'];
    const ts = req.headers['x-signature-timestamp'];
    const ok = sig && ts && crypto.verify(null, Buffer.from(ts + raw), key, Buffer.from(sig, 'hex'));
    if (!ok) return res.writeHead(401).end();
    const i = JSON.parse(raw);
    res.writeHead(200, { 'content-type': 'application/json' });
    if (i.type === 1) return res.end(JSON.stringify({ type: 1 })); // PING -> PONG
    res.end(JSON.stringify({ type: 4, data: { content: 'Olá!', flags: 64 } }));
  });
}).listen(8080);