Skip to content
Menú Academia

La API send-message a fondo: botones, callbacks y modales

Una guía de implementación completa de POST /v1/send-message/ — anatomía del request, hilos, identidad, los cinco tipos de callback, modal_body, códigos de error y los flags de CLI que los generan.

deep-diveDesarrolladorOps11 min read

POST /v1/send-message/ es un único endpoint que hace cinco trabajos: envía un mensaje, encadena un reporte en hilo, suplanta una identidad, edita un envío anterior y — la parte que la mayoría de equipos subutiliza — convierte el mensaje en una pequeña pieza de UI. Esta guía es la versión a nivel de implementación del caso de uso insignia de automatización de chat: cada campo, cada tipo de callback, cada código de error y los flags de CLI que arman el mismo payload sin escribir JSON a mano.

Anatomía del request

El cuerpo del request se agrupa en seis frentes. Rara vez usarás todos en una sola llamada, pero toda integración en producción termina tocando la mayoría a lo largo de su vida.

Grupo Campos
Targeting target_users, target_channels, target_teams, skip_users_on_time_off
Contenido message, messages, image_url, metadata
Hilos bot_message_id, thread_responses
Identidad send_as_user, platform_settings
Botones buttons (arreglo de objetos Button)
Configuración de plataforma platform_settings (overrides exclusivos de Slack)

Se requiere al menos uno de target_users / target_channels / target_teams (missing_targets si no). target_channels acepta un string simple con el id del canal, o un objeto { id, channel_type?, thread? } — usa thread para responder dentro de un hilo existente de la plataforma en lugar de iniciar uno nuevo. Se requiere message (string) o messages (arreglo 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"]}'

La respuesta es {"bot_message_id": "$db/<uuid>"} — un id generado por el servidor que guardas para editar después, o el valor que tú mismo enviaste en el request para controlarlo.

Hilos en una sola llamada

thread_responses publica un mensaje padre más hasta 10 respuestas, un nivel de profundidad, en un solo request — sin llamadas adicionales:

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"}
    ]
  }'

La respuesta devuelve bot_message_id para el padre más un id por cada respuesta en thread_responses — cada uno editable de forma independiente después, con el mismo truco de reutilizar bot_message_id. El threading se renderiza nativamente en canales y DMs de Slack; en Microsoft Teams, Discord y Google Chat, las respuestas se anidan dentro de canales pero llegan planas en DMs (igual llegan, solo sin anidación visual).

Editando en el lugar

Vuelve a hacer POST con el mismo bot_message_id dentro de 72 horas para editar ese mensaje en lugar de enviar uno nuevo:

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)"}'

Los overrides de identidad (bot_username, bot_icon_url, bot_icon_emoji) se ignoran al editar — la plataforma conserva la identidad original del mensaje. Los botones se preservan (round-trip): los existentes sobreviven la edición a menos que envíes nuevos flags de botones.

Identidad: bot personalizado vs. send-as-user

Dos formas independientes de cambiar de quién parece venir un mensaje:

  • Identidad de bot personalizada (cualquier plan) — platform_settings.bot_username más bot_icon_url o bot_icon_emoji (solo Slack, requiere el scope chat:write.customize; sin él, Slack cae silenciosamente en la identidad de bot por defecto y la llamada igual devuelve ok: true).
  • send_as_user (solo Slack, solo admin) — publica con el nombre y avatar de Slack de un compañero real. El mensaje sigue saliendo por el token del bot de Dailybot, no por el del usuario.

Son mutuamente excluyentes (send_as_user_conflict). Un UUID inválido en send_as_user falla del lado del cliente; un UUID bien formado que no resuelve a un usuario devuelve send_as_user_not_found; un llamante que no es admin recibe 403 org_admin_required.

dailybot chat send -c C0123 -m "Deploying the hotfix now" \
  --send-as-user 294bf2cc-e3c7-401d-a1d6-bf20aa64bb33

Los cinco tipos de callback

Cada botón interactivo lleva exactamente uno de cinco callbacks — combinar dos devuelve 400 button_callback_conflict:

Callback Dispara ¿Firmado?
callback_url Tu propio endpoint HTTPS Sí — HMAC + callback_auth opcional
callback_form Abre un formulario interno de Dailybot No — totalmente interno
callback_command Ejecuta un comando ChatOps conocido como quien hizo clic No — totalmente interno
callback_prompt Envía un prompt de texto libre a la IA de Dailybot como quien hizo clic No — totalmente interno
callback_workflow Dispara un workflow api_trigger como quien hizo clic No — totalmente interno

callback_url — tu propio servidor

{"label": "Approve", "button_type": "interactive", "value": "approve",
 "callback_url": "https://ci.example.com/hooks/deploy",
 "callback_auth": {"type": "bearer", "token": "$DEPLOY_CALLBACK_TOKEN"}}

Al hacer clic, Dailybot hace POST de un cuerpo firmado a tu 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 es uno de slack, msteams, discord o hangouts — Google Chat se reporta como hangouts, compara con ese valor. callback_auth (bearer / basic / custom_header) es auth de transporte estática adicional, válida solo con callback_url; la firma HMAC siempre está activa sin importar eso.

callback_form — abrir un formulario interno

{"label": "Submit expense", "button_type": "interactive", "value": "expense_form",
 "callback_form": "$EXPENSE_FORM_UUID"}

Solo UUID — nombres y slugs se rechazan. Formulario desconocido/archivado → 400 button_callback_form_not_found. No se dispara ningún request externo; el propio ciclo de vida del formulario (ver la guía de formularios) toma el control desde ahí.

callback_command — ejecutar un comando ChatOps

{"label": "Run help", "button_type": "interactive", "value": "help",
 "callback_command": "help"}

Se ejecuta como el usuario que hizo clic (sus permisos, su cuota) — máximo 200 caracteres. El prefijo legado "prompt: …" se rechaza (400 button_callback_command_invalid); usa callback_prompt en su lugar.

callback_prompt — pasarle el clic a la IA

{"label": "Summarize thread", "button_type": "interactive", "value": "summarize",
 "callback_prompt": "Summarize the last 20 messages in this channel."}

Vacío o demasiado largo (> 2000 caracteres) → 400 button_callback_prompt_invalid.

callback_workflow — disparar un workflow

{"label": "Trigger rollback", "button_type": "interactive", "value": "rollback",
 "callback_workflow": "$ROLLBACK_WORKFLOW_UUID"}

Solo son elegibles los workflows con tipo de evento api_trigger — encuéntralos con dailybot workflow list --filter api_trigger. UUID desconocido/inactivo/de otra organización → 400 button_callback_workflow_not_found. Los pasos del workflow disparado leen el contexto del clic vía {{trigger.*}}: {{trigger.button_value}}, {{trigger.button_id}}, {{trigger.fields.<name>}} (inputs del modal), {{trigger.user.*}} y más — referencia completa en /es/developers/api/workflows.

Adjunta modal_body a un botón interactivo para abrir primero un modal ligero. Solo existen tres tipos de bloque: text, input, divider — de 1 a 10 bloques, tamaño 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 debe coincidir con ^[a-z][a-z0-9_]{0,62}$ y ser único en el modal — los valores enviados llegan como modal_fields.<name> en callback_url, o {{trigger.fields.<name>}} en callback_workflow. Un modal con bloques input requiere callback_url o callback_workflow (input_without_callback); un modal solo de visualización (text/divider) no requiere ninguno.

response — auto-respuestas instantáneas

Cualquier botón puede llevar un objeto response, mostrado a quien hizo clic de inmediato (en paralelo con el dispatch a callback_url, nunca condicionado a su resultado — el patrón canónico de aprobación lenta):

{"message": "Approved — deploying now.", "buttons": [], "replace_original": false, "ephemeral": false}

response.buttons es recursivo (mismo esquema Button), con un límite de profundidad de anidación de 3, 25 botones por nivel, 16 KiB por botón de nivel superior.

Códigos de error de un vistazo

code Significado
button_link_and_callback_conflict Un botón link mezcló un callback o value junto con url
button_callback_conflict Más de uno de los cinco callbacks en el mismo botón
buttons_count_out_of_range Más de 25 botones en un mensaje
button_modal_body_invalid Tipo de bloque inválido, ≥ 11 bloques, o un modal con inputs sin callback
button_callback_auth_invalid callback_auth configurado sin callback_url, o malformado
send_as_user_conflict send_as_user combinado con bot_username/bot_icon_*

Flags de CLI: los mismos payloads sin JSON escrito a mano

# Flujo de aprobación
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ón que dispara un workflow
dailybot chat send -c C0123456789 -m "Ready to redeploy staging?" \
  --workflow-button "Redeploy staging=$REDEPLOY_WORKFLOW_UUID"

# Cualquier cosa que los flags abreviados no puedan expresar — 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 reenvía las claves sin tocarlas, incluyendo campos futuros que los flags abreviados aún no exponen — es la vía de escape para todo lo que este artículo muestra como JSON crudo arriba.

Todo esto mapea 1:1 con la demo interactiva y las recetas copiar-y-pegar del caso de uso de automatización de chat — lee esa página para la versión orientada a producto del mismo flujo, o vuelve aquí cuando necesites el campo exacto, el código de error exacto, o el flag de CLI exacto.

FAQ

¿Cuál es el cuerpo mínimo de request para POST /v1/send-message/?
Al menos uno de target_users, target_channels o target_teams, más message (un string) o messages (un arreglo de payloads específicos por plataforma). Todo lo demás — hilos, botones, overrides de identidad — es opcional.
¿Cuántos botones interactivos puede llevar un mensaje y qué límites aplican?
Hasta 25 botones por mensaje. Cada botón es exactamente button_type: "link" o "interactive". Un botón interactivo lleva como máximo uno de los cinco callbacks (callback_url, callback_form, callback_command, callback_prompt, callback_workflow) — combinar dos devuelve 400 button_callback_conflict.
¿Cómo verifico que un POST a callback_url realmente vino de Dailybot?
Cada POST de callback lleva X-Dailybot-Signature: t={unix_timestamp}, v1={hex_hmac}. Calcula HMAC-SHA256 sobre el string ASCII "{timestamp}.{raw_body}" usando el secreto de firma de callbacks de tu organización, compáralo en tiempo constante, y rechaza firmas con un timestamp de más de 5 minutos.
¿Cómo edito un mensaje que ya envié?
Vuelve a hacer POST a /v1/send-message/ con el mismo bot_message_id que recibiste (o enviaste) la primera vez, dentro de una ventana de 72 horas. La plataforma conserva la identidad original del bot — bot_username y bot_icon_* se ignoran al editar.
¿Cuál es la diferencia entre callback_workflow y callback_form en un botón?
callback_workflow dispara un workflow interno de Dailybot (solo tipo api_trigger) completamente del lado del servidor — sin POST externo, sin firma. callback_form abre un formulario interno de Dailybot para que quien hizo clic lo llene. Ambos son mutuamente excluyentes con callback_url y entre sí.