Spraxium logoSpraxium

Fundamentos do Signal

Entenda o Signal como um protocolo seguro e assíncrono de entrega de eventos, pensado para arquiteturas de bots. Conheça o contrato do envelope, o modelo de segurança com HMAC, o pipeline de validação em dez regras e casos de uso em produção para integrações entre serviços e bots.

Por que o Signal existe em arquiteturas de bots

Signal foi criado para equipes que operam bots em cenários reais, nos quais vários serviços externos precisam avisar o bot sobre mudanças de domínio sem criar dependências rígidas em tempo de execução. Em vez de acoplar cada API, worker ou painel administrativo diretamente aos internals do bot, você publica eventos padronizados por webhooks do Discord e deixa o bot consumi-los por um único caminho seguro. Isso mantém os limites de responsabilidade claros, porque os sistemas externos ficam focados em produzir eventos de negócio, enquanto o bot fica focado em reagir a eles.

O protocolo usa a própria infraestrutura de webhooks do Discord como camada de transporte. Serviços externos enviam envelopes JSON assinados para um canal privado do Discord, e o módulo Signal do bot escuta as novas mensagens desse canal. Esse desenho evita expor APIs internas do serviço, evita subir servidores de transporte sob medida e aproveita a confiabilidade e as garantias de entrega que o Discord já oferece. Essa separação entre produtor e consumidor é o principal motivo de o Signal escalar melhor do que handlers de webhook improvisados espalhados pelos módulos da aplicação.

Dois pacotes, um protocolo

O ecossistema do Signal é composto por dois pacotes. @spraxium/signal é o receptor no lado do bot, responsável por validar e despachar eventos. @spraxium/signal-client é o emissor leve usado por serviços externos para publicar envelopes assinados.

Visão geral da arquitetura

O ecossistema do Signal se divide em um receptor, o bot, e um ou mais emissores, que são os serviços externos. O emissor monta um envelope assinado, publica esse envelope como mensagem de webhook em um canal privado do Discord, e o listener do bot o captura. O receptor valida o envelope por meio de um pipeline rígido de dez regras antes de despachar o evento para um handler registrado.

Essa arquitetura mantém o processo do bot limpo. Ele nunca expõe portas HTTP de entrada para ingestão de eventos. Em vez disso, aproveita o evento messageCreate do Discord, ao qual o bot já está inscrito. O módulo Signal apenas adiciona um filtro que identifica mensagens de webhook vindas de fontes aprovadas e as encaminha para o pipeline de validação.

ComponentePacoteResponsabilidade
SignalClient@spraxium/signal-client

Monta envelopes, assina com HMAC e entrega via POST de webhook.

SignalModule@spraxium/signal

Módulo global que conecta o pipeline do receptor ao runtime do bot.

SignalValidator@spraxium/signal

Executa dez regras sequenciais de validação sobre mensagens de entrada.

SignalProcessor@spraxium/signal

Orquestra validação, deduplicação por nonce, parsing com Zod e despacho do handler.

SignalRouter@spraxium/signalMapeia nomes de evento para funções de handler registradas.
SignalRegistry@spraxium/signal

Descobre classes com @SignalListener, registra handlers e conecta o listener de messageCreate.

NonceCache@spraxium/signal

Rastreia nonces já processados com TTL de 60 segundos para proteção contra replay.

HmacService@spraxium/signal

Calcula e verifica assinaturas HMAC-SHA256 com comparação segura contra timing attacks.

Estrutura de pastas recomendada

Integração do Signal em um projeto de bot
signal.config.ts
signal.module.ts
guild-config.signal.ts
billing.signal.ts
config-update.schema.ts
billing-status.schema.ts
spraxium.config.ts

O contrato do envelope

Toda mensagem do Signal segue um único formato de envelope. Essa estrutura padronizada garante que a validação de transporte aconteça antes de qualquer código de negócio ser executado. O envelope é um objeto JSON com sete campos, e cada um cumpre um papel específico no pipeline de validação e despacho.

CampoTipoFinalidade
v1

Versão do protocolo usada para controle de compatibilidade. Na versão atual, é sempre 1.

eventstring

Nome do evento de domínio que mapeia para um listener no bot, como config.update.

guildIdstring

Contexto da guild de destino em implantações de bot com múltiplas guilds.

noncestring

UUID exclusivo deste envelope, usado para deduplicação contra replay.

sentAtnumber

Timestamp Unix em milissegundos de quando o envelope foi criado.

signaturestring

Digest hexadecimal HMAC-SHA256 usado para verificar integridade e confiança na origem.

payloadunknown

Dados de domínio livres, entregues ao handler do bot que for resolvido.

example-envelope.json
{
  "v": 1,
  "event": "config.update",
  "guildId": "123456789012345678",
  "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "sentAt": 1712937600000,
  "signature": "8f14e45fceea167a5a36dedd4bea2543",
  "payload": {
    "prefix": "!",
    "language": "en"
  }
}

Modelo de segurança com HMAC

A camada de HMAC é a fronteira de segurança que informa ao bot se um envelope foi produzido por um emissor autorizado e se o conteúdo permaneceu intacto durante o trajeto. A assinatura é calculada concatenando os campos nonce, sentAt e payload em uma única string (${nonce}.${sentAt}.${JSON.stringify(payload)}) e depois aplicando HMAC-SHA256 com uma chave secreta compartilhada.

No lado do bot, o mesmo cálculo é executado e o resultado é comparado com a assinatura recebida por meio de uma função de comparação segura contra timing attacks. Se os valores não coincidirem, o envelope é rejeitado antes de qualquer lógica de negócio ser executada. Isso protege contra três categorias de ataque: adulteração do payload em trânsito, replay com dados alterados e envelopes forjados por serviços não autorizados.

Na prática, o HMAC permite que sua equipe confie em automações vindas de dashboards externos, sistemas de billing ou backoffices de moderação sem expor APIs internas de serviço ao processo do bot. O segredo compartilhado precisa ser o mesmo nos dois lados, emissor e receptor, e nunca deve ser commitado no controle de versão.

O segredo compartilhado precisa ser idêntico

O valor de hmacSecret em defineSignal, no lado do bot, precisa ser idêntico ao valor de hmacSecret passado no construtor de SignalClient. Se houver diferença, mesmo que seja de um único caractere, todo envelope será rejeitado como não confiável.

Pipeline de validação em dez regras

Antes que qualquer handler de domínio seja executado, toda mensagem recebida passa por um pipeline rígido de validação em dez regras, implementado em SignalValidator. Cada regra é avaliada em sequência, e a primeira falha interrompe o pipeline imediatamente. Esse desenho fail-fast garante que mensagens inválidas consumam o mínimo possível de processamento.

RegraVerificaçãoRejeita quando
R1Origem do webhookA mensagem não possui a propriedade webhookId.
R2Identidade do webhook

O valor de webhookId não está no array allowedWebhookIds.

R3Limite de canal

O canal da mensagem não corresponde ao channelId configurado.

R4Parsing de JSON

O conteúdo da mensagem não é um JSON válido ou não é um objeto.

R5Versão do protocoloO campo v não é exatamente 1.
R6Registro do evento

O nome em event não está registrado em nenhum handler do bot.

R7Validade do guild IDO valor de guildId está vazio ou não é uma string.
R8Validade do nonceO valor de nonce está vazio ou não é uma string.
R9Diferença de relógio

O timestamp em sentAt se desvia mais de 5 minutos do relógio do bot.

R10Assinatura HMAC

O valor de signature não corresponde ao digest HMAC-SHA256 recalculado.

Pipeline completo de processamento

Depois que o validador aprova um envelope, o SignalProcessor segue pelas demais etapas até a execução do handler de domínio. Entender o fluxo completo ajuda a depurar problemas de entrega e a raciocinar sobre a ordem das operações durante a análise de incidentes.

Validação em dez regras

O SignalValidator verifica cada mensagem recebida contra dez regras em sequência. A primeira falha rejeita a mensagem e registra uma entrada de diagnóstico quando o modo debug está habilitado. Só mensagens que passam por todas as dez regras seguem para a próxima etapa.

Deduplicação por nonce

O NonceCache verifica se esse nonce já apareceu nos últimos 60 segundos. Se já apareceu, a mensagem é tratada como replay e descartada em silêncio. Se for novo, o nonce é registrado com timestamp. Entradas expiradas são removidas automaticamente na próxima verificação.

Resolução e roteamento do handler

O SignalRouter procura o handler registrado para esse nome de evento. Se nenhum handler existir, a mensagem é registrada como sem rota e descartada. Isso não deveria acontecer porque a regra R6 já confirmou que o evento foi registrado, mas a checagem no router adiciona uma camada extra de defesa.

Validação do payload com Zod

Se o handler resolvido tiver um schema Zod associado nas opções de @OnSignal, o processor executa safeParse sobre o payload. Se a validação falhar, a mensagem é rejeitada com um log de diagnóstico. Se não houver schema configurado, o payload bruto segue sem alterações.

Exclusão opcional da mensagem

Se deleteAfterProcessing estiver habilitado na configuração do Signal, o processor tenta excluir a mensagem de webhook do canal do Discord. Falhas na exclusão são registradas como warning, mas não impedem a execução do handler.

Execução do handler

O processor invoca o handler resolvido com o payload validado e o envelope completo. Erros lançados pelo handler são capturados, registrados e não derrubam o pipeline do Signal. Os demais eventos continuam sendo processados normalmente.

Casos de uso práticos

Caso de usoAderênciaPor que funciona bem
Sistema de billing notificando atualizações de cargoAlta

Sistemas de negócio emitem eventos enquanto o bot aplica alterações de cargo na guild de forma segura e assíncrona.

Backoffice de moderação publicando ações de enforcement

Alta

O backoffice permanece desacoplado e o bot cuida da execução voltada ao Discord de forma independente.

Painel administrativo em Next.js usando Server ActionsAlta

Server Actions publicam eventos assinados diretamente, mantendo a lógica de UI simples e a operação do bot assíncrona.

Pipeline de CI/CD disparando anúncios do botAlta

Etapas do pipeline emitem eventos após deploys bem-sucedidos e o bot publica notas de release formatadas.

Fluxos síncronos de request-responseBaixa

O Signal é assíncrono e unidirecional por definição. Contratos HTTP diretos funcionam melhor para respostas imediatas.

Quando evitar o Signal

Se produtor e consumidor vivem no mesmo grafo de módulos e você não precisa de fronteiras assíncronas, chamadas diretas de serviço são mais simples de entender e mais fáceis de rastrear. O Signal deve ser adotado quando suas garantias operacionais, seu modelo de segurança e os benefícios de desacoplamento realmente fazem diferença, não como padrão para toda interação interna. Para loops síncronos de feedback voltados ao usuário, em que o chamador precisa de resposta imediata, endpoints HTTP ou chamadas diretas à API do Discord continuam sendo escolhas melhores.