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.
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:
- Verificar a assinatura primeiro, retornando
401/403imediatamente em caso de falha (terminal, sem retry). - Validar o formato do payload, retornando
400em entradas malformadas (terminal, sem retry). - Persistir o suficiente para terminar o processamento de forma assíncrona, depois retornar
2xxrapidamente — não deixe a lógica do próprio pipeline bloquear a resposta. - Usar
X-Dailybot-Deliverycomo chave de idempotência: se o mesmo id de entrega chegar duas vezes (o único retry, ou um reclique do lado do cliente antes quedestroy_buttondesative o botão), não dispare o deploy em duplicidade.
Referências cruzadas
- A API send-message, de ponta a ponta para o contrato completo de botões/callbacks sobre o qual este gate se apoia.
/pt/developers/api/messaging#send-messagepara a referência estruturada dos campos./pt/developers/clipara o conjunto completo de flags da CLI.
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.