Primeiro App
Do token ao primeiro evento com o SDK oficial, em uma sentada. Nenhum passo pede infraestrutura sua além de um processo Node.
1. Crie o App e guarde a credencial
No painel do portal, crie a aplicação. A credencial aparece uma vez; copie-a para o ambiente do servidor onde o bot vai rodar. O SDK lê as duas variáveis sozinho.
export TRIVO_APP_TOKEN='trivo_app.SEU_APPLICATION_ID.o-segredo'
export TRIVO_BASE_URL='https://o-endereco-do-seu-trivo'2. Instale o SDK
pnpm add @trivo/sdkUm pacote só: o que o SDK precisa do núcleo do Trivo vem embutido nele, com uma entrada de exports — não há import interno que vire contrato por acidente.
3. Confirme que a credencial funciona
import { createApp } from "@trivo/sdk";
// Sem argumento nenhum: lê TRIVO_APP_TOKEN e TRIVO_BASE_URL do ambiente.
const app = createApp();
console.log(await app.rest.whoAmI());
// → { app: { publicId: "…", name: "Meu Bot" } }É a chamada que responde "a sua credencial vale" sem efeito nenhum. A recusa é uma só, e não distingue: token inexistente, revogado ou de dono sancionado levantam TrivoError com MISSING_CREDENTIAL e a mesma frase.
4. Instale o App num servidor
Antes de mandar o link, abra o App para instalação. Todo App nasce privado, e enquanto ele estiver assim quem abrir o link recebe APP_NOT_FOUND — "esse App não existe", que é a recusa anti-oráculo de propósito e não distingue "não existe" de "não está aberto". A chave fica na ficha do App, no painel, e o que cada visibilidade significa está em Criar e gerenciar um App.
O App só enxerga servidores onde ele foi instalado, e quem instala é uma pessoa com a permissão gerenciar_apps. Copie o link de instalação na ficha do App, no portal, e mande para quem administra o servidor: ele leva à tela que mostra o desenvolvedor, o Application ID e os escopos pedidos, com os sensíveis num bloco à parte e desmarcados.
Nada instala por GET nem por parâmetro. A única mutação é o gesto de quem autoriza.
5. Ouça os eventos e responda
import { createApp } from "@trivo/sdk";
const app = createApp({ intents: ["messages", "interactions"] });
app.on("ready", () => console.log("de pé"));
app.on("message_create", async (evento) => {
if (evento.message.content !== "!ping") return;
await app.rest.sendMessage({
serverId: evento.serverId,
channelId: evento.message.channelId,
content: "pong",
clientRef: `pong:${evento.message.publicId}`,
});
});
await app.connect();Por que mandar clientRef
É a chave de idempotência: o mesmo valor no mesmo canal não grava duas mensagens. Vale a pena sempre que a mensagem for consequência de um evento — uma retomada de conexão reentrega o intervalo perdido, e sem clientRef o seu bot responde duas vezes.
6. Registre um comando
É o que faz o seu App aparecer no compositor. O registro vale na hora — sem espera de propagação —, e sem serverId o comando é global.
await app.rest.createCommand({
command: { type: "chat", name: "ping", description: "responde pong" },
});
app.onInteraction(async (interaction) => {
// `data` é `unknown` de propósito: a carga muda com o tipo da interação,
// e o Trivo não a congela. Estreite antes de ler.
const dados = interaction.data as { command?: { name?: string } };
if (dados.command?.name !== "ping") return;
await interaction.reply("pong");
});O que se registra ali — os três tipos, as opções e os tetos — está em Comandos; o ciclo da resposta, com os prazos, o diferido, o formulário e o autocompletar, está em Interações.
7. Ou receba sem manter conexão aberta
Se o seu App só responde a comandos, um endpoint HTTP é melhor do que um fluxo aberto: a conexão do Gateway é um processo de pé, e são duas vagas por App. Na ficha do App, no painel, o bloco Endpoint de interações salva o endereço e ativa. A verificação da assinatura é obrigatória, e o SDK a faz:
import { InteractionVerifier } from "@trivo/sdk";
const verifier = new InteractionVerifier({
applicationId: app.applicationId,
baseUrl: process.env.TRIVO_BASE_URL!,
});
// req.rawBody exige express.raw({ type: "application/json" }) na rota: o corpo
// tem de ser os BYTES que chegaram, e reserializar troca os bytes.
const resultado = await verifier.verify({ headers: req.headers, body: req.rawBody });
if (!resultado.ok) throw new Error(resultado.reason);O ciclo inteiro do endpoint — provar que ele está de pé, os quatro estados e o que responder a cada tipo de entrega — está em Endpoint de interações.
- Salvar não ativa. O estado vira
probing, e enquanto isso a entrega continua indo pelo Gateway. - Ativar é a prova de vida. O Trivo manda um POST assinado com um desafio, e o seu servidor precisa devolvê-lo em 3 segundos — é o que
interactionResponse.probemonta. - Falhar seguidamente desliga. Nesse estado as interações expiram sem entrega — o painel mostra, e o caminho de volta é testar de novo.
O que o SDK já resolve, e você não escreve
- Queda de rede. Reconexão com espera crescente e ruído, e retomada por
Last-Event-ID— você recebe o intervalo perdido. - Silêncio. O vigia do batimento derruba a conexão morta que o sistema operacional ainda acha viva, e o recuo para a caixa JSON acontece sozinho quando o fluxo abre e não flui.
- Parar quando mandam parar. Fechamento com
reconnect: falsenão vira laço: o SDK desiste e avisa poronStatus. - 429. O
Retry-Afteré obedecido, com ruído para cima e teto de sanidade. - Contrapressão e ordem.
eventQueueLimitpara de ler o gateway em vez de acumular memória, eeventOrdering: "channel"serializa por canal quando você precisa. - Versão da API. O cabeçalho viaja sozinho, nos dois canais.
- O que ele NÃO faz por você: a idempotência. O
clientRefé repassado como você o escreveu, e nunca inventado — só você sabe o que torna duas escritas "a mesma". Um valor gerado pelo SDK mudaria a cada reentrega, que é exatamente quando ele precisa NÃO mudar.
Referência de transporte
O que essas linhas viram no fio
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.
Útil para depurar com o log do seu proxy aberto, ou para conferir uma resposta que não bate. A credencial é Authorization: Bearer, sempre em cabeçalho:
GET /api/apps/me → { "app": { "publicId", "name" } }
GET /api/apps/eventos?intents=… → text/event-stream
POST /api/servidores/{s}/canais/{c}/mensagens
POST /api/apps/me/comandos