A API send-message a fundo: botões, callbacks e modais
Um guia de implementação completo do POST /v1/send-message/ — anatomia do request, threads, identidade, os cinco tipos de callback, modal_body, códigos de erro e os flags de CLI que os geram.
POST /v1/send-message/ é um único endpoint que faz cinco trabalhos: envia uma mensagem, encadeia um relatório em thread, personifica uma identidade, edita um envio anterior e — a parte que a maioria das equipes subutiliza — transforma a mensagem em uma pequena peça de UI. Este guia é a versão em nível de implementação do caso de uso principal de automação de chat: cada campo, cada tipo de callback, cada código de erro e os flags de CLI que montam o mesmo payload sem escrever JSON manualmente.
Anatomia do request
O corpo do request se agrupa em seis frentes. Você raramente usará todas em uma única chamada, mas toda integração em produção acaba tocando a maioria delas ao longo do tempo.
| Grupo | Campos |
|---|---|
| Targeting | target_users, target_channels, target_teams, skip_users_on_time_off |
| Conteúdo | message, messages, image_url, metadata |
| Threads | bot_message_id, thread_responses |
| Identidade | send_as_user, platform_settings |
| Botões | buttons (array de objetos Button) |
| Configurações de plataforma | platform_settings (overrides exclusivos do Slack) |
É necessário pelo menos um de target_users / target_channels / target_teams (missing_targets caso contrário). target_channels aceita uma string simples com o id do canal, ou um objeto { id, channel_type?, thread? } — use thread para responder dentro de uma thread existente da plataforma em vez de iniciar uma nova. É necessário message (string) ou messages (array de payloads por plataforma).
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 finished","target_channels":["C0123456789"]}'
A resposta é {"bot_message_id": "$db/<uuid>"} — um id gerado pelo servidor que você guarda para editar depois, ou o valor que você mesmo enviou no request para controlá-lo.
Threads em uma única chamada
thread_responses publica uma mensagem pai mais até 10 respostas, um nível de profundidade, em um único request — sem chamadas adicionais:
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": "Daily report — 2026-07-24",
"target_channels": ["C0123456789"],
"bot_message_id": "daily-report-2026-07-24",
"thread_responses": [
{"message": "alice: shipped the billing migration"},
{"message": "bob: on PTO, no updates"}
]
}'
A resposta retorna bot_message_id para o pai mais um id por resposta em thread_responses — cada um editável de forma independente depois, com o mesmo truque de reutilizar bot_message_id. O threading é renderizado nativamente em canais e DMs do Slack; no Microsoft Teams, Discord e Google Chat, as respostas se aninham dentro de canais mas chegam planas em DMs (ainda chegam, só sem aninhamento visual).
Editando no lugar
Faça POST novamente com o mesmo bot_message_id dentro de 72 horas para editar aquela mensagem em vez de enviar uma nova:
curl -sS -X POST 'https://api.dailybot.com/v1/send-message/' \
-H 'X-API-KEY: $DAILYBOT_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"target_channels":["C0123456789"],"bot_message_id":"daily-report-2026-07-24","message":"Daily report — 2026-07-24 (updated)"}'
Os overrides de identidade (bot_username, bot_icon_url, bot_icon_emoji) são ignorados ao editar — a plataforma mantém a identidade original da mensagem. Os botões são preservados (round-trip): os existentes sobrevivem à edição a menos que você envie novos flags de botões.
Identidade: bot personalizado vs. send-as-user
Duas formas independentes de mudar de quem parece vir uma mensagem:
- Identidade de bot personalizada (qualquer plano) —
platform_settings.bot_usernamemaisbot_icon_urloubot_icon_emoji(somente Slack, requer o escopochat:write.customize; sem ele, o Slack cai silenciosamente na identidade de bot padrão e a chamada ainda retornaok: true). send_as_user(somente Slack, somente admin) — publica com o nome e avatar do Slack de um colega real. A mensagem ainda sai pelo token do bot do Dailybot, não pelo do usuário.
São mutuamente exclusivos (send_as_user_conflict). Um UUID inválido em send_as_user falha no lado do cliente; um UUID bem formado que não resolve a um usuário retorna send_as_user_not_found; um chamador que não é admin recebe 403 org_admin_required.
dailybot chat send -c C0123 -m "Deploying the hotfix now" \
--send-as-user 294bf2cc-e3c7-401d-a1d6-bf20aa64bb33
Os cinco tipos de callback
Cada botão interativo carrega exatamente um dos cinco callbacks — combinar dois retorna 400 button_callback_conflict:
| Callback | Dispara | Assinado? |
|---|---|---|
callback_url |
Seu próprio endpoint HTTPS | Sim — HMAC + callback_auth opcional |
callback_form |
Abre um formulário interno do Dailybot | Não — totalmente interno |
callback_command |
Executa um comando ChatOps conhecido como quem clicou | Não — totalmente interno |
callback_prompt |
Envia um prompt de texto livre para a IA do Dailybot como quem clicou | Não — totalmente interno |
callback_workflow |
Dispara um workflow api_trigger como quem clicou |
Não — totalmente interno |
callback_url — seu próprio servidor
{"label": "Approve", "button_type": "interactive", "value": "approve",
"callback_url": "https://ci.example.com/hooks/deploy",
"callback_auth": {"type": "bearer", "token": "$DEPLOY_CALLBACK_TOKEN"}}
Ao clicar, o Dailybot faz POST de um corpo assinado para o seu callback_url:
{
"event": "button_click",
"bot_message_id": "$db/ae007b43-...",
"button": {"id": "$btn/...", "value": "approve"},
"user": {"id": "...", "email": "...", "display_name": "...", "external_id": "U01ABCDEFG"},
"organization": {"id": "...", "name": "..."},
"platform": "slack",
"channel": {"id": "D0123456", "type": "im"},
"modal_fields": null,
"metadata": {"campaign": "coffee-approvals"},
"sent_at": "2026-07-22T14:30:00Z",
"clicked_at": "2026-07-22T14:31:12Z"
}
platform é um de slack, msteams, discord ou hangouts — o Google Chat é reportado como hangouts, compare com esse valor. callback_auth (bearer / basic / custom_header) é auth de transporte estática adicional, válida apenas com callback_url; a assinatura HMAC está sempre ativa independentemente disso.
callback_form — abrir um formulário interno
{"label": "Submit expense", "button_type": "interactive", "value": "expense_form",
"callback_form": "$EXPENSE_FORM_UUID"}
Somente UUID — nomes e slugs são rejeitados. Formulário desconhecido/arquivado → 400 button_callback_form_not_found. Nenhum request externo é disparado; o próprio ciclo de vida do formulário (veja o aprofundamento de formulários) assume o controle a partir daí.
callback_command — executar um comando ChatOps
{"label": "Run help", "button_type": "interactive", "value": "help",
"callback_command": "help"}
Executa como o usuário que clicou (suas permissões, sua cota) — máximo 200 caracteres. O prefixo legado "prompt: …" é rejeitado (400 button_callback_command_invalid); use callback_prompt em vez disso.
callback_prompt — repassar o clique para a IA
{"label": "Summarize thread", "button_type": "interactive", "value": "summarize",
"callback_prompt": "Summarize the last 20 messages in this channel."}
Em branco ou muito longo (> 2000 caracteres) → 400 button_callback_prompt_invalid.
callback_workflow — disparar um workflow
{"label": "Trigger rollback", "button_type": "interactive", "value": "rollback",
"callback_workflow": "$ROLLBACK_WORKFLOW_UUID"}
Somente workflows com tipo de evento api_trigger são elegíveis — encontre-os com dailybot workflow list --filter api_trigger. UUID desconhecido/inativo/de outra organização → 400 button_callback_workflow_not_found. Os passos do workflow disparado leem o contexto do clique via {{trigger.*}}: {{trigger.button_value}}, {{trigger.button_id}}, {{trigger.fields.<name>}} (inputs do modal), {{trigger.user.*}} e mais — referência completa em /pt/developers/api/workflows.
modal_body — coletando informações antes do callback disparar
Anexe modal_body a um botão interativo para abrir primeiro um modal leve. Existem apenas três tipos de bloco: text, input, divider — de 1 a 10 blocos, tamanho serializado ≤ 8 KiB.
{
"title": "Log an update",
"submit_label": "Save",
"blocks": [
{"type": "text", "text": "Share a quick status update."},
{"type": "input", "name": "update_text", "label": "Update", "multiline": true, "required": true},
{"type": "divider"}
]
}
name deve corresponder a ^[a-z][a-z0-9_]{0,62}$ e ser único no modal — os valores enviados chegam como modal_fields.<name> em callback_url, ou {{trigger.fields.<name>}} em callback_workflow. Um modal com blocos input requer callback_url ou callback_workflow (input_without_callback); um modal somente de exibição (text/divider) não requer nenhum dos dois.
response — auto-respostas instantâneas
Qualquer botão pode carregar um objeto response, mostrado a quem clicou imediatamente (em paralelo com o dispatch do callback_url, nunca condicionado ao seu resultado — o padrão canônico de aprovação lenta):
{"message": "Approved — deploying now.", "buttons": [], "replace_original": false, "ephemeral": false}
response.buttons é recursivo (mesmo schema Button), com limite de profundidade de aninhamento de 3, 25 botões por nível, 16 KiB por botão de nível superior.
Códigos de erro em resumo
code |
Significado |
|---|---|
button_link_and_callback_conflict |
Um botão link misturou um callback ou value junto com url |
button_callback_conflict |
Mais de um dos cinco callbacks no mesmo botão |
buttons_count_out_of_range |
Mais de 25 botões em uma mensagem |
button_modal_body_invalid |
Tipo de bloco inválido, ≥ 11 blocos, ou um modal com inputs sem callback |
button_callback_auth_invalid |
callback_auth configurado sem callback_url, ou malformado |
send_as_user_conflict |
send_as_user combinado com bot_username/bot_icon_* |
Flags de CLI: os mesmos payloads sem JSON escrito à mão
# Fluxo de aprovação
dailybot chat send -c C0123456789 -m "Deploy 4.12.0 to production?" \
--approve-button "Approve=approved" --reject-button "Reject=rejected" \
--callback-url "https://ci.example.com/hooks/deploy" \
--callback-bearer "$DEPLOY_CALLBACK_TOKEN"
# Botão que dispara um workflow
dailybot chat send -c C0123456789 -m "Ready to redeploy staging?" \
--workflow-button "Redeploy staging=$REDEPLOY_WORKFLOW_UUID"
# Qualquer coisa que os flags abreviados não conseguem expressar — pass-through completo de JSON
dailybot chat send -c C0123456789 -m "Log a quick update" \
--buttons '[{"label":"Add update","button_type":"interactive","value":"log_update",
"callback_url":"https://ci.example.com/hooks/log-update",
"modal_body":{"title":"Log an update","submit_label":"Save",
"blocks":[{"type":"input","name":"update_text","label":"Update","multiline":true,"required":true}]}}]'
--buttons repassa as chaves sem alterá-las, incluindo campos futuros que os flags abreviados ainda não expõem — é a via de escape para tudo que este artigo mostra como JSON puro acima.
Tudo isso mapeia 1:1 com a demo interativa e as receitas de copiar-e-colar do caso de uso de automação de chat — leia essa página para a versão orientada a produto do mesmo fluxo, ou volte aqui sempre que precisar do campo exato, do código de erro exato, ou do flag de CLI exato.
FAQ
- Qual é o corpo mínimo de request para POST /v1/send-message/?
- Pelo menos um de target_users, target_channels ou target_teams, mais message (uma string) ou messages (um array de payloads específicos por plataforma). Todo o resto — threads, botões, overrides de identidade — é opcional.
- Quantos botões interativos uma mensagem pode ter e quais limites se aplicam?
- Até 25 botões por mensagem. Cada botão é exatamente button_type: "link" ou "interactive". Um botão interativo carrega no máximo um dos cinco callbacks (callback_url, callback_form, callback_command, callback_prompt, callback_workflow) — combinar dois retorna 400 button_callback_conflict.
- Como verifico se um POST em callback_url realmente veio do Dailybot?
- Todo POST de callback carrega X-Dailybot-Signature: t={unix_timestamp}, v1={hex_hmac}. Calcule HMAC-SHA256 sobre a string ASCII "{timestamp}.{raw_body}" usando o segredo de assinatura de callbacks da sua organização, compare em tempo constante, e rejeite assinaturas com timestamp de mais de 5 minutos.
- Como edito uma mensagem que já enviei?
- Faça POST novamente em /v1/send-message/ com o mesmo bot_message_id que você recebeu (ou enviou) da primeira vez, dentro de uma janela de 72 horas. A plataforma mantém a identidade original do bot — bot_username e bot_icon_* são ignorados em edições.
- Qual é a diferença entre callback_workflow e callback_form em um botão?
- callback_workflow dispara um workflow interno do Dailybot (apenas tipo api_trigger) totalmente no lado do servidor — sem POST externo, sem assinatura. callback_form abre um formulário interno do Dailybot para quem clicou preencher. Ambos são mutuamente exclusivos com callback_url e entre si.