Skip to content
Menu Academia

Configurando gates de aprovação de CI/CD com callbacks assinados

Pause um pipeline em um gate de deploy, publique botões de aprovar/rejeitar e verifique o callback assinado que seu servidor recebe — contrato completo, retries e timeouts.

deep-diveDesenvolvedorOps9 min read

Um pipeline de release que pausa e pergunta a um humano é um contrato de mão dupla: o Dailybot publica o pedido de aprovação, e o webhook do seu sistema de CI recebe o clique. Erre a verificação de assinatura ou o tratamento de retries e você acaba bloqueando deploys por falsos negativos ou — pior — confiando em um request não verificado. Este é o contrato exato, de ponta a ponta.

O gate: dois botões, uma callback URL

Publique o gate como uma mensagem interativa com um botão de aprovar e um de rejeitar, ambos apontando para a mesma callback_url, ambos carregando um token bearer via callback_auth para que seu webhook possa autenticar o transporte além da assinatura obrigatória:

curl -sS -X POST 'https://api.dailybot.com/v1/send-message/' \
  -H 'X-API-KEY: $DAILYBOT_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Deploy 4.12.0 to production — approve?",
    "target_channels": ["C0123456789"],
    "buttons": [
      {"label": "Approve", "button_type": "interactive", "value": "approved",
       "callback_url": "https://ci.example.com/hooks/deploy",
       "callback_auth": {"type": "bearer", "token": "$DEPLOY_CALLBACK_TOKEN"},
       "response": {"message": "Approved — deploying now."}},
      {"label": "Reject", "button_type": "interactive", "value": "rejected",
       "callback_url": "https://ci.example.com/hooks/deploy",
       "callback_auth": {"type": "bearer", "token": "$DEPLOY_CALLBACK_TOKEN"},
       "response": {"message": "Rejected."}}
    ]
  }'

O atalho de CLI para exatamente este padrão:

dailybot chat send -c C0123456789 -m "Deploy 4.12.0 to production — approve?" \
  --approve-button "Approve=approved" \
  --reject-button "Reject=rejected" \
  --callback-url "https://ci.example.com/hooks/deploy" \
  --callback-bearer "$DEPLOY_CALLBACK_TOKEN"

--approve-button / --reject-button recebem "Label=value" (separador de sinal de igual); --callback-url e --callback-bearer se aplicam a ambos. Prefira --callback-bearer "$TOKEN" a um token literal para que nada acabe no histórico do shell ou nas listas de processos. Observe o response imediato na versão curl: ele dispara em paralelo com o POST de saída, e não fica condicionado à resposta do seu webhook — a confirmação é instantânea, e a decisão real do deploy acontece do seu lado, de forma assíncrona (o padrão de aprovação lenta). Se precisar atualizar a mensagem depois que seu pipeline terminar, edite-a mais tarde reenviando um POST com o mesmo bot_message_id dentro de 72 horas.

O que o seu webhook recebe

O Dailybot faz POST exatamente com esta forma para callback_url no clique:

POST https://ci.example.com/hooks/deploy
Content-Type: application/json
User-Agent: Dailybot-Chatbot/1.0
X-Dailybot-Event: button_click
X-Dailybot-Delivery: 5e2d1a9c-...-uuid
X-Dailybot-Timestamp: 1782001872
X-Dailybot-Signature: t=1782001872, v1=6d9a3e...hex_hmac
Authorization: Bearer $DEPLOY_CALLBACK_TOKEN
{
  "event": "button_click",
  "bot_message_id": "$db/ae007b43-dde2-4fa9-bce3-71fb0975a249",
  "button": {"id": "$btn/...", "value": "approved"},
  "user": {"id": "...", "email": "...", "display_name": "...", "external_id": "U01ABCDEFG"},
  "organization": {"id": "...", "name": "..."},
  "platform": "slack",
  "channel": {"id": "C0123456789", "type": "channel"},
  "modal_fields": null,
  "metadata": {},
  "sent_at": "2026-07-22T14:30:00Z",
  "clicked_at": "2026-07-22T14:31:12Z"
}

Leia button.value ("approved" ou "rejected") para decidir o que o seu pipeline faz em seguida; use user.display_name / user.email para atribuir a decisão no seu log de deploy; bot_message_id permite editar a mensagem original depois para refletir o resultado.

Verificando a assinatura — nunca pule isso

X-Dailybot-Signature é HMAC-SHA256 calculado sobre a string ASCII "{unix_timestamp}.{raw_body}" usando o segredo de assinatura de callbacks da sua organização (gerado automaticamente na primeira vez que você envia uma mensagem com callback_url; obtenha-o com o administrador da sua organização — ele nunca é retornado pela API pública). Verifique-a em todo request, com ou sem callback_auth:

const crypto = require('crypto');
function verify(rawBody, sigHeader, secretB64) {
  const secret = Buffer.from(secretB64, 'base64url');
  const parts = Object.fromEntries(sigHeader.split(',').map(s => s.trim().split('=')));
  const ts = parseInt(parts.t, 10);
  if (Math.abs(Date.now() / 1000 - ts) > 300) return false; // 5-minute replay window
  const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(parts.v1, 'hex'));
}

Leia o corpo bruto do request — antes de qualquer parsing de JSON — para calcular a assinatura; fazer o parsing primeiro e re-serializar produzirá uma string de bytes diferente e uma incompatibilidade falsa de assinatura.

callback_auth — adicional, não um substituto

callback_auth só é válido junto com callback_url (400 button_callback_auth_invalid em qualquer outra combinação). Três tipos, cada um com seus próprios campos:

type Campos Enviado como
bearer token (≤ 4096) Authorization: Bearer <token>
basic username, password Authorization: Basic <base64>
custom_header header_name, header_value <header_name>: <header_value>

header_name deve ser um token válido conforme RFC 7230 e não pode ser host, content-length, content-type, transfer-encoding, connection, user-agent, ou qualquer coisa que comece com x-dailybot-. As credenciais são write-only — nunca retornadas por nenhuma API de leitura, nunca registradas em log. Trate callback_auth como uma segunda tranca na porta que seu gateway já exige, não como um substituto para a verificação HMAC acima.

Comportamento de retry e timeout

O Dailybot tenta novamente o POST de callback de saída uma vez, com backoff de 500 ms, em uma resposta 5xx, 429 ou um erro de rede. Um 2xx bem-sucedido completa o dispatch. Qualquer outro 4xx é tratado como terminal e não é repetido — se o seu webhook validar o payload e encontrá-lo malformado, retorne 4xx deliberadamente para impedir que o Dailybot repita um request que nunca vai funcionar. Projete seu endpoint para:

  1. Verificar a assinatura primeiro, retornando 401/403 imediatamente em caso de falha (terminal, sem retry).
  2. Validar o formato do payload, retornando 400 em entradas malformadas (terminal, sem retry).
  3. Persistir o suficiente para terminar o processamento de forma assíncrona, depois retornar 2xx rapidamente — não deixe a lógica do próprio pipeline bloquear a resposta.
  4. Usar X-Dailybot-Delivery como chave de idempotência: se o mesmo id de entrega chegar duas vezes (o único retry, ou um reclique do lado do cliente antes que destroy_button desative o botão), não dispare o deploy em duplicidade.

Referências cruzadas

Navegue pelo hub de Soluções para o passeio voltado ao produto deste mesmo fluxo, com a demo de chat simulado ao vivo.

FAQ

Como faço para condicionar um deploy a um clique humano a partir do Dailybot?
Envie uma mensagem com dois botões interativos que ambos definem callback_url para o webhook do seu CI e callback_auth para um token bearer. Seu pipeline aguarda o POST assinado que o seu webhook recebe, lê button.value (por exemplo, "approved" ou "rejected") e retoma ou cancela de acordo.
Como verifico se o POST de callback realmente veio do Dailybot e não é um request falsificado?
Todo POST de callback_url carrega X-Dailybot-Signature: t={timestamp}, v1={hex_hmac}. Recalcule o HMAC-SHA256 sobre "{timestamp}.{raw_body}" usando o segredo de assinatura de callbacks da sua organização, compare em tempo constante e rejeite qualquer coisa mais antiga que uma janela de 5 minutos.
O que acontece se meu endpoint de callback estiver momentaneamente fora do ar quando alguém clicar em aprovar?
O Dailybot tenta novamente o POST de saída uma vez, com backoff de 500ms, em respostas 5xx, 429 ou erros de rede. Um 2xx bem-sucedido completa o dispatch; qualquer outro 4xx é tratado como terminal e descartado — então um 4xx do seu endpoint não será repetido.
callback_auth pode substituir a verificação de assinatura?
Não. callback_auth (bearer, basic ou custom_header) é uma autenticação de transporte estática e adicional para gateways que a exigem — ela não substitui a assinatura HMAC, que está sempre presente e sempre deve ser verificada.
callback_auth é válido em todo tipo de botão?
Não — callback_auth só é válido junto com callback_url. Defini-lo junto com callback_form, callback_command, callback_prompt ou callback_workflow retorna 400 button_callback_auth_invalid.