Interações
Alguém acionou algo do seu App, e você tem poucos segundos para dizer o que faz. A entrega chega pelo Gateway ou por um POST assinado no seu endpoint — e nunca pelos dois.
O seu código
app.onInteraction(async (interaction) => {
await interaction.reply("pong");
});É o mesmo handler nos dois caminhos de entrega — pelo Gateway ou por POST assinado no seu endpoint. O SDK esconde a diferença de propósito: ela é de transporte, e trocar de caminho não pode ser reescrever o bot.
app.onInteraction(async (interaction) => {
await interaction.defer(); // "estou pensando"
const resposta = await algoLento();
await interaction.deferredReply(resposta);
await interaction.followUp("e mais isto", { private: true });
});decline() existe para quando o App decide não responder — é diferente de sumir, e quem acionou vê a frase certa.
app.onInteraction(async (interaction) => {
await interaction.defer(); // fecha o ACK dentro dos 3 s
await interaction.deferredReply(await algoLento()); // 60 s a partir daqui
});O ciclo, em uma volta
pessoa aciona
→ o Trivo grava a interação e ENTREGA ao App
→ o App CONFIRMA em segundos: reply, defer, modal ou decline
(no autocomplete: suggest ou decline, em 1 s)
→ se foi defer, o App RESPONDE em até 60 s
→ e pode acrescentar até 5 SEGUIMENTOS em 10 minutosA máquina é de mão única: confirmada, uma interação não volta a estar aberta. A transição é atômica no servidor, contra as duas portas de confirmação ao mesmo tempo — a primeira que chegar ganha, e a segunda recebe INTERACTION_ALREADY_CONFIRMED.
Os tipos
O campo type da interação diz o que foi acionado. O vocabulário é fechado:
| Tipo | O que é |
|---|---|
command | Um comando de barra. |
command_on_user | Um comando sobre uma pessoa, pelo menu de clique direito. |
command_on_message | Um comando sobre uma mensagem, pelo menu de clique direito. |
autocomplete | O cliente pedindo sugestões enquanto a pessoa digita. Prazo bem menor. |
component | Um botão ou menu de seleção de uma resposta anterior. |
form | Um formulário enviado. |
Referência de transporte
Caminho 1: pelo Gateway
O que viaja no fio, para transparência, depuração e compatibilidade — não é um segundo jeito de construir App. Se você está escrevendo um App, o SDK já faz isto.
Se o seu App tem uma conexão aberta e nenhum endpoint HTTP ativo, a interação chega como o evento interaction_create, da família interactions — e o SDK a entrega ao seu onInteraction já embrulhada:
{
"type": "interaction_create",
"serverId": "1234567890123456789",
"appId": "9876543210987654321",
"interaction": {
"id": "3f6b1f7a-0e2c-4a1d-9b8e-5c4d3e2f1a0b",
"type": "command",
"serverId": "1234567890123456789",
"channelId": "1111111111111111111",
"userId": "2222222222222222222",
"data": { },
"expiresInMs": 3000
}
}expiresInMs é duração, e não instante: o relógio do prazo é o do servidor. Uma reentrega pela janela de retomada chega com o valor já descontado, e pode chegar em zero — nesse caso a confirmação será recusada, e está certo assim.
Caminho 2: o seu endpoint
O outro caminho de entrega é um POST assinado no seu servidor, e ele substitui o Gateway em vez de somar-se a ele: com o endpoint ativo, a interação vai por lá e só por lá. Configurar, provar que ele está de pé e entender os quatro estados é assunto de Endpoint de interações; o que prova que a entrega é nossa está em Assinatura das entregas.
O que não muda é o seu código: o onInteraction acima é o mesmo nos dois caminhos, e é para isso que o SDK esconde a diferença.
Responder
await interaction.reply("pong"); // confirma e responde
await interaction.reply("só para você", { private: true });
await interaction.defer(); // confirma sem responder ainda
await interaction.deferredReply("pronto"); // depois do defer, em até 60 s
await interaction.followUp("e mais isto"); // até 5, em 10 minutos
await interaction.decline(); // confirma dizendo que não faz
await interaction.showModal({ // abre um formulário
id: "sugestao",
title: "Mandar sugestão",
fields: [
{ type: "text", id: "titulo", label: "Título", required: true, maxLength: 60 },
{ type: "paragraph", id: "corpo", label: "Detalhes" },
],
});
await interaction.suggest([{ name, value }]); // só em autocompletePor baixo são três rotas, e elas respondem { "interaction": { "id", "state", "followups", "expiresAt" } } — mais messageId quando a resposta foi pública.
Editar ou apagar o que você acabou de responder
Uma resposta pública grava uma mensagem no canal, e o desfecho traz o endereço dela. É o que fecha o ciclo interaction → resposta → seguimento → edit/delete: as rotas de mensagem já aceitam credencial de App, e faltava só o App saber para onde apontar.
const { interaction: feito } = await interaction.reply("pong");
if (feito.messageId) {
await app.rest.editMessage({
serverId: interaction.serverId,
channelId: interaction.channelId,
messageId: feito.messageId,
content: "pong (corrigido)",
});
}messageId é opcional porque ele legitimamente não vem: na resposta privada não existe linha em mensagens, e no defer e no decline não há resposta ainda. Devolver null ali sugeriria uma mensagem que se perdeu. deferredReply e followUp públicos também o trazem.
Referência de transporte
As três rotas por baixo dos sete gestos
O que viaja no fio, para transparência, depuração e compatibilidade — não é um segundo jeito de construir App. Se você está escrevendo um App, o SDK já faz isto.
| Rota | Corpo | Quando |
|---|---|---|
POST /api/interacoes/{interacao}/confirmar | type: reply | defer | decline | modal | suggest. reply exige content não vazio; suggest exige choices, e só serve o autocomplete. | A primeira resposta, dentro do prazo de confirmação. |
POST /api/interacoes/{interacao}/resposta | content; opcionais private e components. | Depois de defer, dentro de 60 s. |
POST /api/interacoes/{interacao}/seguimento | content; opcionais private e components. | Até 5, em 10 minutos. Não muda o estado. |
O atalho do corpo
No caminho HTTP, o corpo da sua resposta ao POST assinado é literalmente o mesmo JSON de /confirmar — e usá-lo economiza uma ida à rede dentro de um prazo de 3 segundos. Não é um mecanismo paralelo: é um atalho para uma rota que continua existindo, e as duas portas disputam a mesma transição atômica.
// a sua resposta ao POST do Trivo
{ "type": "reply", "content": "pong" }Um corpo que não case com o esquema não vira erro: você respondeu 2xx, o contador de falhas do endpoint zera, mas a interação continua aberta e expira. Se você não consegue deduplicar entregas, não responda privado pelo corpo — confirme com {"type":"defer"} e mande a resposta pela rota REST.
O autocompletar
Quando alguém digita numa opção que você registrou com "autocomplete": true, o Trivo abre uma interação de tipo autocomplete e a entrega pelo mesmo caminho de todas as outras — mesma credencial, mesma assinatura, mesmo Gateway. O que muda é o relógio: 1 segundo, e não 3, porque do outro lado alguém está com o dedo no teclado.
// o que chega em data
{ "command": { "publicId": "5001", "name": "clima", "path": "clima" },
"arguments": [{ "name": "cidade", "type": "string", "value": "mar" }],
"focused": "cidade",
"focusedType": "string",
"focusedLimits": { "maxLength": 40 } }
// o que você responde, na mesma volta
{ "type": "suggest", "choices": [
{ "name": "Maringá", "value": "maringa" },
{ "name": "Marabá", "value": "maraba" } ] }Atrasar não é erro na cara de quem digita. Se você não responder a tempo, ou responder torto, a lista simplesmente não aparece — quem está escrevendo segue escrevendo. É de propósito: sugestão é ajuda, e ajuda que falha não pode virar susto no meio de uma frase. O que você perde é o balde e a reputação do endpoint, como em qualquer outra interação.
A resposta privada não é mensagem
Com private: true, o texto vira um evento endereçado a quem acionou. Ele não entra no histórico de ninguém, não persiste, não aparece em busca e não volta numa recarga. É o teto de 5 seguimentos que impede o token de virar canal de mensagem direta por fora do funil.