Skip to content
ver .md sin procesar

Mensajería

Envía mensajes del bot a usuarios, canales y equipos. Las respuestas en hilo se limitan a 10 por mensaje padre, con un solo nivel de anidamiento. Puedes editar un mensaje dentro de las 72 horas reenviando el mismo `bot_message_id`.

POST/v1/send-message/API keyCLI Authsend_message_cli

Enviar un mensaje del bot

Envía un mensaje a usuarios, canales o equipos. Adjunta hasta 25 botones interactivos por mensaje — enlaces, URLs de callback firmadas, modales, formularios internos, comandos, prompts de IA, workflows y auto-respuestas. El mismo endpoint maneja respuestas en hilo (hasta 10 respuestas por mensaje padre, un nivel de profundidad), impersonación del bot vía `platform_settings` (nombre/avatar/ephemeral en Slack) y edición de mensajes — reenviar con el mismo `bot_message_id` dentro de 72 horas edita el mensaje original.

Cuerpo de la solicitud

Destinatarios

NombreTipoRequeridoDescripción
target_usersarray<string>OpcionalUUIDs de usuario, emails o IDs externos. Se requiere al menos uno de target_users / target_channels / target_teams.
target_channelsarray<string | object>OpcionalIDs de canal como strings, u objetos { id, channel_type?, thread? }. Usa thread (timestamp/id padre) para responder en un hilo existente.
target_teamsarray<string>OpcionalUUIDs de equipo. Todos los miembros del equipo reciben el mensaje.
skip_users_on_time_offbooleanOpcionalSi es true, se omiten usuarios marcados como OOO / de permiso.

Contenido

NombreTipoRequeridoDescripción
messagestringOpcionalTexto plano o un subconjunto HTML seguro. Requerido si no se envía messages.
messagesarrayOpcionalPayloads específicos por plataforma (avanzado). Requerido si se omite message.
image_urlstring (https)OpcionalURL HTTPS de imagen para adjuntar al mensaje.
metadataobjectOpcionalMetadata personalizada libre adjunta al mensaje y reenviada en los cuerpos de callback.

Hilos y edición

NombreTipoRequeridoDescripción
bot_message_idstringOpcionalID de idempotencia personalizado. Reenvía el mismo valor dentro de 72 horas para editar el mensaje original.
thread_responsesarray<{ message: string }>OpcionalHasta 10 respuestas publicadas bajo el mismo padre en una sola llamada. Solo un nivel de anidación.

Identidad e impersonación

NombreTipoRequeridoDescripción
send_as_userstring (UUID)OpcionalSolo Slack. Solo admin. Publica con el nombre y avatar de otro miembro. Mutuamente excluyente con platform_settings.bot_username, bot_icon_url y bot_icon_emoji.

Botones

NombreTipoRequeridoDescripción
buttonsarray<Button>OpcionalHasta 25 botones interactivos o de enlace. Contrato completo campo a campo: Objeto Button ↓. El servidor genera button_id como $btn/<uuid4> (los ids del cliente se sobrescriben).

Configuración de plataforma

NombreTipoRequeridoDescripción
platform_settingsobjectOpcionalOverrides de identidad/ephemeral solo Slack: { bot_username?, bot_icon_url? | bot_icon_emoji?, is_ephemeral? }. Otras plataformas ignoran el bloque. Mutuamente excluyente con send_as_user.

Objeto Button (`buttons[]`)

Contrato completo de cada entrada en buttons (máx. 25 por mensaje). Los cinco callbacks (callback_url, callback_form, callback_command, callback_prompt, callback_workflow) son mutuamente excluyentes. modal_body se combina con callback_url O callback_workflow. response se combina con todo. Ver también la sección Interactive buttons de la página para la matriz, verificación de firma y recetas.

NombreTipoRequeridoDescripción
labelstring (≤ 40)RequeridoTexto mostrado en el botón.
label_after_clickstring (≤ 40)OpcionalReemplaza label después del clic (o tras enviar el modal). Por defecto igual a label.
button_type"link" | "interactive"Requeridolink abre url en el navegador. interactive dispara una de las capacidades de callback abajo.
urlstring (https)OpcionalRequerido cuando button_type = "link". URL que abre el usuario.
valuestring (≤ 2000)OpcionalRequerido cuando button_type = "interactive". Se ecoa en los POST de callback. También como {{trigger.button_value}} en workflows.
callback_urlstring (https, ≤ 2048)OpcionalPOST firmado de salida al hacer clic y al enviar el modal. Mutuamente excluyente con los otros cuatro callbacks. Ver prosa Interactive buttons para verificación HMAC.
modal_bodyobjectOpcionalAbre un modal al hacer clic. Combina con callback_url O callback_workflow. Los modales con inputs requieren uno de esos (input_without_callback). Contrato completo: objeto modal_body ↓ y blocks ↓.
callback_formstring (form UUID)OpcionalAbre un formulario interno de Dailybot. Solo UUID (lista vía /v1/forms/). Mutuamente excluyente. Desconocido/archivado → 400 button_callback_form_not_found.
callback_commandstring (≤ 200)OpcionalEjecuta un comando conocido de Dailybot como el clicker (p. ej. help). El prefijo legado "prompt: …" se rechaza — usa callback_prompt. Mutuamente excluyente.
callback_promptstring (≤ 2000)OpcionalPrompt de IA en texto libre ejecutado como el usuario que hace clic (su cuota/permisos). Vacío o demasiado largo → 400 button_callback_prompt_invalid. Mutuamente excluyente.
callback_workflowstring (workflow UUID)OpcionalDispara un workflow activo api_trigger en la org del llamante. Solo UUID. Desconocido/inactivo/otra org → 400 button_callback_workflow_not_found. Mutuamente excluyente.
responseobjectOpcionalAuto-respuesta al clicker: { message (≤2000, requerido), buttons? (recursivo), replace_original?, ephemeral? }. Combina con cualquier forma de botón. Contrato completo: objeto response ↓.
callback_authobjectOpcionalAuth de transporte estática para el POST de salida — solo válida con callback_url. Aditiva a la firma HMAC. Credenciales solo escritura. Contrato completo: objeto callback_auth ↓.
destroy_buttonbooleanOpcionalPor defecto true. Si es false, el botón sigue clicable; cada clic es un dispatch nuevo con un X-Dailybot-Delivery nuevo.
button_idstring ($btn/<uuid4>)OpcionalGenerado por el servidor. Se ecoa en cada callback. Los valores del cliente se sobrescriben — no envíes este campo.
platform_settingsobjectOpcionalOverrides raros por plataforma (legado). Prefiere los campos top-level del botón.
payloadobjectOpcionalPayload opaco libre. Los campos interactivos nuevos deben ir al top-level del botón, no dentro de payload.

Objeto modal_body

Se adjunta a un Button interactive para abrir un modal ligero. Tamaño serializado ≤ 8 KiB. Los bloques se detallan en [modal_body.blocks[] ↓](#send-message-modal-block).

NombreTipoRequeridoDescripción
titlestring (≤ 200)RequeridoTítulo del modal.
submit_labelstring (≤ 200)OpcionalEtiqueta del botón de envío. Por defecto: "Submit".
blocksarray (1..10)RequeridoExactamente los tipos text, input y divider (1–10). modal_body serializado ≤ 8 KiB. Slack lo renderiza nativo; otras plataformas lo recogen conversacionalmente. Campos del bloque: [modal_body.blocks[] ↓](#send-message-modal-block).

Elemento de modal_body.blocks[]

1–10 bloques dentro de modal_body.blocks. Solo se permiten text, input y divider.

NombreTipoRequeridoDescripción
type"text" | "input" | "divider"RequeridoTipo de bloque. Solo se aceptan estos tres valores.
textstring (≤ 3000)OpcionalRequerido para type: "text". Texto solo de visualización.
namestringOpcionalRequerido para type: "input". Debe coincidir con ^[a-z][a-z0-9_]{0,62}$ y ser único en el modal. Los valores llegan como modal_fields.<name> / {{trigger.fields.<name>}}.
labelstring (≤ 200)OpcionalRequerido para type: "input". Etiqueta del campo mostrada al usuario.
multilinebooleanOpcionalSolo input. Por defecto false.
requiredbooleanOpcionalSolo input. Por defecto false.
placeholderstring (≤ 200)OpcionalSolo input. Placeholder opcional.
max_lengthinteger (1..3000)OpcionalSolo input. Longitud máxima opcional del valor.
defaultstringOpcionalSolo input. Valor prefijado opcional.

Objeto response

Auto-respuesta opcional en cualquier Button. Con callback_url, se envía de inmediato en paralelo al POST de salida (patrón slow-approval).

NombreTipoRequeridoDescripción
messagestring (≤ 2000)RequeridoTexto de auto-respuesta mostrado al clicker.
buttonsarray<Button>OpcionalBotones anidados recursivos con el mismo esquema Button. Límites: profundidad ≤ 3, ≤ 25 botones/nivel, ≤ 16 KiB por botón top-level. Campos Button: Objeto Button ↑.
replace_originalbooleanOpcionalReemplaza el mensaje original en lugar de publicar uno nuevo. Por defecto false.
ephemeralbooleanOpcionalSolo Slack; se ignora en otras plataformas. Por defecto false.

Objeto callback_auth

Auth de transporte estática opcional en un Button. Solo válida junto con callback_url. Siempre aditiva a X-Dailybot-Signature.

NombreTipoRequeridoDescripción
type"bearer" | "basic" | "custom_header"RequeridoTipo de auth. Exactamente los campos del tipo elegido — extras se rechazan (button_callback_auth_invalid).
tokenstring (≤ 4096)OpcionalRequerido para type: "bearer". Se envía como Authorization: Bearer <token>.
usernamestringOpcionalRequerido para type: "basic" (con password). Se envía como Authorization: Basic <base64>.
passwordstringOpcionalRequerido para type: "basic" (con username).
header_namestring (RFC 7230 token)OpcionalRequerido para type: "custom_header". Nombres denegados: host, content-length, content-type, transfer-encoding, connection, user-agent, y todo lo que empiece por x-dailybot-.
header_valuestringOpcionalRequerido para type: "custom_header". Se envía como <header_name>: <header_value>.

Respuesta

NombreTipoRequeridoDescripción
bot_message_idstringRequeridoEl ID que enviaste, o uno generado $db/<uuid>. Reutilízalo en 72h para editar.
thread_responsesarray<string>OpcionalIDs de respuestas en hilo (solo si enviaste thread_responses). Cada uno se edita igual que bot_message_id.

Errores

EstadoCuándo
400Error de validación — revisa el campo `code`: button_link_and_callback_conflict, button_callback_conflict, button_callback_url_invalid, button_modal_body_invalid, button_callback_form_not_found, button_callback_command_invalid, button_callback_prompt_invalid, button_callback_workflow_not_found, button_response_invalid, button_callback_auth_invalid, buttons_count_out_of_range; además send_as_user_conflict, send_as_user_invalid_uuid, send_as_user_not_found, invalid_thread_responses, missing_targets.
401Credencial ausente, inválida o expirada.
403Autenticado pero sin permiso — `org_admin_required` cuando un no-admin usa `send_as_user`; el scoping por rol del CLI devuelve `cli_send_message_target_not_allowed`.
429Rate-limit alcanzado. Respeta el header `Retry-After`.
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 done","target_channels":["C0123456789"]}'

Probarlo

Este es un asistente de solo copia — la solicitud no se envía desde tu navegador. Pega el comando en tu terminal para ejecutarlo.

  • Threads: pasa `thread_responses` (máx. 10) para publicar padre + respuestas en una sola llamada. Responde a un mensaje existente agregando `thread` dentro de un objeto en `target_channels`: `{ id, thread: <ts_o_id_padre> }`.
  • Editar: reenvía con el mismo `bot_message_id` dentro de 72h. Los campos de identidad (`bot_username`, `bot_icon_*`) se ignoran al editar — la plataforma conserva la identidad original.
  • Impersonación: `platform_settings.bot_username` más `bot_icon_url` O `bot_icon_emoji` cambian la apariencia del bot para ese mensaje. Solo Slack hoy; otras plataformas ignoran el bloque de identidad.
  • Ephemeral (solo Slack): usa `platform_settings.is_ephemeral: true` junto con `target_users` para enviar un mensaje privado dentro del canal, visible solo para esos usuarios.
  • Scoping por rol del CLI: admin/manager alcanza a cualquier usuario/equipo/canal público; miembro de equipo alcanza a sus compañeros + canales públicos; invitado solo se envía mensajes a sí mismo.
  • Multi-plataforma: funciona en Slack, Microsoft Teams, Discord y Google Chat. El threading en DMs varía — Teams / Discord / Google Chat publican planos en DMs.
  • Botones interactivos: los cinco callbacks (`callback_url`, `callback_form`, `callback_command`, `callback_prompt`, `callback_workflow`) son mutuamente excluyentes en un mismo botón (`button_callback_conflict`). `modal_body` se combina con `callback_url` O `callback_workflow` (no con callbacks internos). Las auto-respuestas `response` se combinan con cualquier forma de botón.
  • Callbacks firmados: los botones con `callback_url` reciben POST firmados con HMAC vía `X-Dailybot-Signature` (y `X-Dailybot-Event`, `X-Dailybot-Delivery`, `X-Dailybot-Timestamp`). Google Chat se reporta como plataforma `hangouts` en el cuerpo del callback — compara con ese valor, no con "gchat" ni "googlechat".
  • Consulta la sección Interactive buttons en la página para snippets de verificación de firma (Node.js / Python), el esquema de bloques de `modal_body`, la matriz de compatibilidad y los tipos de callback-auth.
POST/v1/open-conversation/API key

Open a conversation (API-Key only)

Open a conversation (API-Key only)

Errores

EstadoCuándo
401Missing/invalid/expired credential
403Authenticated but not permitted
429Throttled - Retry-After header set
400Validation error
curl -sS -X POST 'https://api.dailybot.com/v1/open-conversation/' -H 'X-API-KEY: $DAILYBOT_API_KEY'

Probarlo

Este es un asistente de solo copia — la solicitud no se envía desde tu navegador. Pega el comando en tu terminal para ejecutarlo.

POST/v1/send-email/API key

Send an email (API-Key only)

Body: to, subject, body_html, body_text.

Errores

EstadoCuándo
401Missing/invalid/expired credential
403Authenticated but not permitted
429Throttled - Retry-After header set
400Validation error
curl -sS -X POST 'https://api.dailybot.com/v1/send-email/' -H 'X-API-KEY: $DAILYBOT_API_KEY'

Probarlo

Este es un asistente de solo copia — la solicitud no se envía desde tu navegador. Pega el comando en tu terminal para ejecutarlo.

Constructor de botones

Compón botones interactivos visualmente y copia el JSON exacto de buttons, el comando dailybot chat send, o el body de curl.

Corrige antes de enviar

  • #1: La etiqueta es requerida.
  • #1: Los botones interactivos requieren un value.

Salida generada

[
  {
    "label": "",
    "button_type": "interactive"
  }
]

La API pública de Dailybot expone endpoints de mensajería para entregar mensajes del bot, emails y conversaciones privadas en Slack, Microsoft Teams, Discord y Google Chat. Esta página incluye las tablas de campos estructuradas de POST /v1/send-message/, POST /v1/send-email/ y POST /v1/open-conversation/ (incluido el contrato completo de Button / modal_body / response / callback_auth), además de la sustitución de identidad send_as_user y la guía detallada de botones interactivos.

Constructor de botones

Debajo de la referencia de send-message, un Constructor de botones interactivo te permite componer botones visualmente — de enlace, simples, aprobar/rechazar, disparadores de workflow, formulario, comando, prompt y modal — y copiar el array buttons JSON exacto, el comando dailybot chat send equivalente (usando --link-button, --button, --approve-button/--reject-button con --callback-url/--callback-bearer, o --workflow-button, con reserva a --buttons '<json>' para lo que esos flags no puedan expresar), o un body de curl listo para ejecutar contra POST /v1/send-message/.

Sustitución de identidad send_as_user (solo Slack)

send_as_user permite a un llamador autorizado publicar un mensaje que parece provenir de otro usuario: el mensaje muestra el nombre para mostrar y el avatar de Slack de ese usuario en lugar de la identidad del bot. Es útil para workflows automatizados que necesitan presentar mensajes desde la perspectiva de un miembro específico del equipo.

Requisitos:

  • El llamador debe ser administrador de la organización o tener una clave propiedad de un administrador.
  • El usuario objetivo (UUID de send_as_user) debe estar activo y en la misma organización.
  • Solo aplica en Slack: las demás plataformas ignoran el campo y vuelven a la identidad estándar del bot.
  • send_as_user es mutuamente exclusivo con bot_username, bot_icon_url y bot_icon_emoji. Combinarlos devuelve 400 con code: "send_as_user_conflict".

Cuando se establece send_as_user, Dailybot busca la identidad de Slack conectada del usuario y la usa como autor del mensaje. El mensaje sigue enviándose a través del token del bot de Dailybot; no usa el token de Slack del usuario.

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "The sprint is closed and all tickets are resolved.",
    "target_channels": ["C0123456789"],
    "send_as_user": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }'

Códigos de error para send_as_user:

code HTTP Cuándo
send_as_user_conflict 400 send_as_user combinado con bot_username, bot_icon_url o bot_icon_emoji
send_as_user_invalid_uuid 400 El formato del UUID no es válido
send_as_user_not_found 400 Usuario no encontrado, inactivo o en otra organización
org_admin_required 403 El llamador no es administrador

Botones interactivos

Cada mensaje puede llevar hasta 25 botones renderizados como elementos interactivos nativos en Slack, Microsoft Teams, Discord y Google Chat. Se admiten dos valores de button_type:

  • link — al hacer clic se abre la url en el navegador del usuario.
  • interactive — al hacer clic activa una de las cuatro capacidades descritas a continuación.

Campos del botón

Campo Tipo Cuándo Notas
label string (≤ 40 chars) requerido Se renderiza en el botón.
label_after_click string (≤ 40) opcional Reemplaza label después del clic. Por defecto label.
button_type "link" | "interactive" requerido Ver abajo.
url string (https) requerido cuando button_type = "link" La URL que abre el usuario.
value string (≤ 2000) requerido cuando button_type = "interactive" Se devuelve en el cuerpo del POST de callback para que el llamador sepa qué botón se pulsó.
callback_url string (https, ≤ 2048) opcional (interactive) URL de callback que recibe un POST firmado al hacer clic y al enviar el modal. Mutuamente exclusivo con callback_form y callback_command. Ver URLs de callback interactivas abajo.
modal_body object opcional (interactive) Abre un modal al hacer clic. Requiere callback_url si el modal tiene campos de entrada. Ver Botones modales abajo.
callback_form string (form UUID) opcional (interactive) Activa un formulario interno de Dailybot, referenciado por su UUID. Mutuamente exclusivo con callback_url y callback_command. Ver Disparadores de formularios internos abajo.
callback_command string (≤ 200) opcional (interactive) Activa un comando interno CONOCIDO de Dailybot (p. ej. help). Se rechaza el prefijo heredado "prompt: …" — usa callback_prompt. Mutuamente exclusivo con los demás callbacks.
callback_prompt string (≤ 2000) opcional (interactive) Prompt de IA en texto libre ejecutado como el usuario que hace clic. Mutuamente exclusivo con los demás callbacks. Ver Disparadores de prompts de IA abajo.
callback_workflow string (workflow UUID) opcional (interactive) Activa un workflow interno de Dailybot, referenciado por su UUID. Mutuamente exclusivo con los demás callbacks. Ver Disparadores de workflows abajo.
response object opcional Respuesta automática al usuario que hace clic: {message (≤2000, required), buttons? (recursive), replace_original?, ephemeral?}. Se combina con CUALQUIER forma de botón. Ver Respuestas automáticas abajo.
callback_auth object opcional (requiere callback_url) Auth de transporte estática para el POST saliente (bearer | basic | custom_header) — aditiva a la firma HMAC siempre activa. Ver Autenticación de callback abajo.
destroy_button bool opcional (por defecto true) Cuando es false, el botón sigue siendo clicable tras el primer clic.
platform_settings object opcional Overrides por plataforma (rara vez necesarios).
payload object opcional Payload extra libre; opaco para la API. Los campos nuevos anteriores deben estar en el nivel superior, no dentro de payload.

Cada botón interactivo del lado del servidor lleva un button_id = "$btn/<uuid4>" generado por el servidor que se devuelve en cada cuerpo de callback para que el llamador pueda identificar cada clic de forma única. Los valores de button_id proporcionados por el cliente se sobrescriben silenciosamente.

Nota de compatibilidad: payload.callback_config y los campos value de acciones de integraciones antiguas siguen funcionando y son aditivos; prefiere los campos de nivel superior anteriores para integraciones nuevas.

Matriz de compatibilidad

✔ = permitidos juntos, ✖ = rechazado. Los cinco callbacks son mutuamente exclusivos entre sí (button_callback_conflict).

callback_url callback_form callback_command callback_prompt callback_workflow modal_body response
callback_url
callback_form
callback_command
callback_prompt
callback_workflow
modal_body
response

Un modal con bloques input requiere callback_url O callback_workflow (400 button_modal_body_invalid, detail input_without_callback).

label_after_click y destroy_button se combinan con todo (incluido response): destroy_button: false mantiene el botón clicable; cada clic es un nuevo despacho con un id X-Dailybot-Delivery nuevo; en botones modales el cambio de label_after_click ocurre al ENVIAR el modal, no al abrirlo, así que un modal cancelado deja el botón clicable. Un botón interactivo simple puede llevar response sin ningún callback (el caso “Skip”).

URLs de callback interactivas

Un botón interactive con callback_url recibe un HTTP POST de Dailybot cuando el usuario hace clic. Es la forma canónica para flujos de aprobación, feedback y triaje ligero construidos sobre /v1/send-message/.

Ejemplo de solicitud:

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Juan wants to buy a coffee. Approve?",
    "target_users": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
    "buttons": [
      { "label": "Yes", "button_type": "interactive", "value": "approve",
        "callback_url": "https://approvals.acme.example/v1/decisions/req_9812" },
      { "label": "No",  "button_type": "interactive", "value": "deny",
        "callback_url": "https://approvals.acme.example/v1/decisions/req_9812" }
    ]
  }'

Cuerpo del POST de callback — Dailybot lo envía cuando el usuario hace clic:

POST https://approvals.acme.example/v1/decisions/req_9812
Content-Type: application/json
User-Agent: Dailybot-Chatbot/1.0
X-Dailybot-Event: button_click
X-Dailybot-Delivery: 5e2d…-uuid
X-Dailybot-Timestamp: 1782001872
X-Dailybot-Signature: t=1782001872, v1=6d9a3e…hex_hmac
{
  "event": "button_click",
  "bot_message_id": "$db/ae007b43-dde2-4fa9-bce3-71fb0975a249",
  "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"
}

Valores de platform. Uno de slack, msteams, discord o hangouts. Google Chat se entrega como hangouts (el identificador histórico de plataforma) — coincide con ese valor, no con “gchat” ni “googlechat”. sent_at se marca cuando el mensaje de la plataforma se entregó realmente; clicked_at cuando el usuario hizo clic. metadata se devuelve tal cual desde tu llamada original a /v1/send-message/.

Verificación de firma. Cada POST lleva una firma HMAC-SHA256 calculada con el secreto de firma de callback de tu organización sobre la cadena ASCII "{unix_timestamp}.{raw_body}".

Node.js:

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;
  const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(parts.v1, 'hex'));
}

Python:

import hmac, hashlib, base64, time
def verify(raw_body: bytes, sig_header: str, secret_b64: str) -> bool:
    secret = base64.urlsafe_b64decode(secret_b64 + '==')
    parts = dict(p.strip().split('=', 1) for p in sig_header.split(','))
    ts = int(parts['t'])
    if abs(time.time() - ts) > 300:  # 5 min replay window
        return False
    expected = hmac.new(secret, f"{ts}.{raw_body.decode()}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts['v1'])

Idempotencia. X-Dailybot-Delivery es único por intento de clic. Si el POST saliente de Dailybot se reintenta, se envía el mismo id de entrega — úsalo como clave de deduplicación.

Reintentos. Dailybot reintenta una vez con un backoff de 500 ms en errores 5xx / 429 / de red. Un 2xx exitoso completa el fanout; cualquier otro 4xx se trata como terminal y se descarta.

Botones modales

Añade un objeto modal_body a un botón interactive para abrir un modal ligero al hacer clic. Al enviar, los valores de entrada del modal se publican en callback_url junto con event: "modal_submit" y el value del botón pulsado.

Esquema de modal_body:

Los tipos de bloque admitidos son exactamente text, input y divider. El modal_body serializado debe ser ≤ 8 KiB. Los valores name de entrada deben coincidir con ^[a-z][a-z0-9_]{0,62}$ y ser únicos dentro del modal.

{
  "title": "string, ≤ 200 chars, required",
  "submit_label": "string, ≤ 200 chars, default 'Submit'",
  "blocks": [
    // 1..10 blocks; supported block types:
    { "type": "text", "text": "≤ 3000 chars" },
    {
      "type": "input",
      "name": "snake_case_identifier",   // required, unique per modal; regex ^[a-z][a-z0-9_]{0,62}$
      "label": "Field label, ≤ 200",     // required
      "multiline": true,                  // optional, default false
      "required": true,                   // optional, default false
      "max_length": 1000,                 // optional, ≤ 3000
      "placeholder": "≤ 200",             // optional
      "default": "prefilled value"        // optional
    },
    { "type": "divider" }
  ]
}

Ejemplo de solicitud (aprobar con comentario):

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Sprint retro — how did it feel?",
    "target_teams": ["team_engineering_uuid"],
    "buttons": [
      { "label": "🚀 Great",   "button_type": "interactive", "value": "great",
        "callback_url": "https://retros.acme.example/v1/sprint-42" },
      { "label": "😐 Fine",    "button_type": "interactive", "value": "fine",
        "callback_url": "https://retros.acme.example/v1/sprint-42" },
      {
        "label": "📝 Add feedback",
        "button_type": "interactive",
        "value": "feedback",
        "callback_url": "https://retros.acme.example/v1/sprint-42",
        "modal_body": {
          "title": "Sprint feedback",
          "submit_label": "Send",
          "blocks": [
            { "type": "text", "text": "Anything on your mind about this sprint?" },
            {
              "type": "input",
              "name": "sentiment_note",
              "label": "Your feedback",
              "multiline": true,
              "required": true,
              "max_length": 1000,
              "placeholder": "One or two sentences is perfect."
            }
          ]
        }
      }
    ]
  }'

Cuerpo del POST de envío de modal (Dailybot lo publica en callback_url):

{
  "event": "modal_submit",
  "bot_message_id": "$db/…",
  "button":       { "id": "$btn/…", "value": "feedback" },
  "user":         { "id": "…", "email": "…", "display_name": "…", "external_id": "U…" },
  "organization": { "id": "…", "name": "…" },
  "platform":     "slack",
  "channel":      { "id": "C…", "type": "channel" },
  "modal_fields": {
    "sentiment_note": "Retro was great, ship-per-day cadence is working."
  },
  "metadata":     { },
  "sent_at":      "2026-07-22T14:30:00Z",
  "clicked_at":   "2026-07-22T14:31:12Z"
}

Mismas cabeceras / firma que button_click; solo difieren event y modal_fields.

Los modales solo de visualización (bloques con solo text y divider, sin input) se permiten sin callback_url — al pulsar Submit solo se cierra el modal y no se envía ningún POST.

Disparadores de formularios internos

Un botón interactive con callback_form abre un formulario de Dailybot al hacer clic. No se llama a ningún servicio externo; se ignora el callback_url del llamador (y se rechaza si ambos están definidos).

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Need to request access to a system?",
    "target_channels": ["C0AF0FBT23D"],
    "buttons": [
      { "label": "Open access-request form",
        "button_type": "interactive",
        "value": "open_access_form",
        "callback_form": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
    ]
  }'

callback_form debe ser el UUID de un formulario de Dailybot en la organización del llamador (lista tus formularios vía /v1/forms/ para encontrarlo). Los nombres y slugs se rechazan: los nombres de formulario son mutables y ambiguos. Formularios desconocidos, archivados o inaccesibles → 400 button_callback_form_not_found. El formulario lo completa el usuario mediante el flujo de formularios existente; consulta /v1/forms/ para leer las respuestas resultantes.

Disparadores de comandos internos

Un botón interactive con callback_command ejecuta un comando interno CONOCIDO de Dailybot al hacer clic (p. ej. help, kudos, checkin). Máx. 200 caracteres.

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Need help getting started?",
    "target_users": ["…"],
    "buttons": [
      { "label": "Open help",
        "button_type": "interactive",
        "value": "help",
        "callback_command": "help" }
    ]
  }'

Sin prefijo prompt:. Se eliminó la convención heredada "prompt: <text>". Un callback_command que empiece por prompt: (sin distinguir mayúsculas) se rechaza con 400 button_callback_command_invalid — usa el campo dedicado callback_prompt en su lugar.

Atribución. El comando se ejecuta bajo la identidad del usuario que hace clic (alcance de rol, atribución de uso), no la del remitente. Los llamadores no pueden usar callback_command para hacer que otro usuario ejecute un comando que no tiene permitido.

Los comandos desconocidos (no en la lista de comandos integrados) fallan suavemente con una respuesta localizada “That command isn’t available” al usuario que hizo clic; no hay error para el remitente.

Disparadores de prompts de IA

Un botón interactive con callback_prompt envía un prompt de texto libre (≤ 2000 caracteres) al asistente de IA de Dailybot al hacer clic, atribuido al usuario que hace clic (su cuota de IA, sus permisos).

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Curious about last week?",
    "target_users": ["…"],
    "buttons": [
      { "label": "Ask the assistant",
        "button_type": "interactive",
        "value": "ask_ai",
        "callback_prompt": "Summarize incidents from the past 7 days." }
    ]
  }'

Prompts en blanco o demasiado largos → 400 button_callback_prompt_invalid.

Disparadores de workflows

Un botón interactive con callback_workflow activa un workflow interno de Dailybot al hacer clic, atribuido al usuario que hace clic. callback_workflow debe ser el UUID de un workflow en la organización del llamador — se rechazan nombres/slugs. El workflow debe existir, pertenecer a la organización y estar activo; de lo contrario 400 button_callback_workflow_not_found.

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Deploy is ready.",
    "target_users": ["…"],
    "buttons": [
      { "label": "Run release workflow",
        "button_type": "interactive",
        "value": "run_release",
        "callback_workflow": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
    ]
  }'

No se envía ningún POST externo ni interviene material de firma: el disparador es totalmente interno.

Variables de disparo. Los pasos del workflow activado pueden referenciar el contexto del clic mediante el espacio de nombres {{trigger.*}}: {{trigger.source}} (api | button_click | modal_submit), {{trigger.button_value}}, {{trigger.button_id}}, {{trigger.fields.<name>}} (entradas del modal), {{trigger.clicked_at}}, {{trigger.body.*}} (payload sin procesar), {{trigger.user.*}} (uuid, full_name, first_name, email, role del usuario que hizo clic) y {{trigger.triggered_by_user_uuid}}. Para ramificación simple por valor, apunta varios botones al MISMO workflow con distintos value y ramifica en {{trigger.button_value}}. Consulta /developers/api/workflows para la referencia completa de variables de disparo.

modal_body se combina con callback_workflow: el clic abre el modal y, al enviar, los valores de los campos se entregan al workflow como {{trigger.fields.<input.name>}} — sin servidor externo.

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Report an incident:",
    "target_users": ["…"],
    "buttons": [
      { "label": "Report", "button_type": "interactive", "value": "report",
        "callback_workflow": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "modal_body": {
          "title": "New incident",
          "blocks": [
            { "type": "input", "name": "summary", "label": "What happened?",
              "multiline": true, "required": true }
          ]
        } }
    ]
  }'

El workflow (tipo de disparo api_trigger) puede usar entonces {{trigger.fields.summary}} en cualquier paso: rellenar una respuesta de formulario, componer un mensaje, alimentar un prompt de IA. Los botones modal-a-workflow no llevan secreto de firma (ruta interna). Un modal con bloques input y SIN callback_url ni callback_workflow se rechaza (input_without_callback).

Respuestas automáticas

Cualquier botón (incluido un botón interactivo simple sin callback) puede llevar un objeto response — una respuesta automática mostrada al usuario que hace clic:

{
  "message": "string — required, ≤ 2000 chars",
  "buttons": [],
  "replace_original": false,
  "ephemeral": false
}

El caso “Skip” — un botón cuya única función es reconocer:

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Time for your weekly reflection!",
    "target_users": ["…"],
    "buttons": [
      { "label": "Start", "button_type": "interactive", "value": "start",
        "callback_form": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" },
      { "label": "Skip this week", "button_type": "interactive", "value": "skip",
        "response": { "message": "No problem — see you next week! 👋" } }
    ]
  }'

Momento. Con callback_url, la respuesta se envía inmediatamente al hacer clic, en paralelo con el POST saliente — es feedback instantáneo al usuario (“Processing your approval…”), nunca condicionado al resultado del despacho. Este es el patrón canónico de aprobación lenta: reconocer al instante vía response, luego que tu servidor haga seguimiento a su ritmo con un nuevo POST /v1/send-message/ o editando el mensaje original dentro de la ventana de 72 h de bot_message_id. Con modal_body, la respuesta se envía tras el ENVÍO del modal. Con los callbacks internos (callback_form / callback_command / callback_prompt / callback_workflow), la respuesta se envía ANTES de la entrega.

replace_original — reemplaza el mensaje de origen en lugar de publicar uno nuevo. ephemeral — solo Slack; se ignora en el resto.

Botones recursivos. response.buttons usa el mismo esquema Button y se valida de forma recursiva: profundidad máxima de anidamiento 3, ≤ 25 botones por nivel, button_id generado por el servidor en cada profundidad y un límite de tamaño serializado total de 16 KiB por botón de nivel superior. Violaciones → 400 button_response_invalid con detail uno de response_message_required, response_message_too_long, nesting_too_deep, too_many_buttons, button_too_large.

Autenticación de callback

Cada POST de callback está SIEMPRE firmado con la cabecera HMAC X-Dailybot-Signature (ver Secreto de firma de callback). Si tu endpoint además requiere credenciales estáticas — token de API gateway, basic auth, cabecera fija — adjunta un objeto callback_auth. Solo es válido junto con callback_url.

curl -X POST "https://api.dailybot.com/v1/send-message/" \
  -H "X-API-KEY: $DAILYBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Approve the vendor payment?",
    "target_users": ["…"],
    "buttons": [
      { "label": "Approve", "button_type": "interactive", "value": "approve",
        "callback_url": "https://approvals.acme.example/v1/req_77",
        "callback_auth": { "type": "bearer", "token": "acme-gw-token-…" } }
    ]
  }'

Tipos (exactamente los campos del tipo elegido — los extras se rechazan):

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 debe ser un token de cabecera RFC 7230 válido y no puede ser uno de host, content-length, content-type, transfer-encoding, connection, user-agent, ni nada que empiece por x-dailybot-. Violaciones → 400 button_callback_auth_invalid.

Las credenciales son solo de escritura: nunca las devuelve ninguna API de lectura ni aparecen en logs. La firma HMAC sigue siendo obligatoria — verifícala incluso cuando también exijas auth estática.

Códigos de error de botones interactivos

Todos son HTTP 400. La forma de respuesta sigue el envelope existente ({ "detail": "…", "code": "…" }).

code Cuándo
button_link_and_callback_conflict Un botón link se combinó con value, modal_body, response, callback_auth o cualquier campo de callback.
button_callback_conflict Un botón interactive combina más de uno de callback_url, callback_form, callback_command, callback_prompt, callback_workflow, o combina modal_body con un callback interno.
button_callback_url_invalid callback_url sin esquema, no https, mal formada, más de 2048 caracteres o resuelve a un rango privado / loopback / IMDS.
button_modal_body_invalid modal_body falló la validación estructural: tipo de bloque incorrecto, ≥ 11 bloques, name faltante / duplicado, campo demasiado grande, o modal con entradas pero sin callback_url / callback_workflow (input_without_callback).
button_callback_form_not_found callback_form no resuelve a un formulario en la organización del llamador.
button_callback_command_invalid callback_command supera 200 caracteres o usa el prefijo prompt: eliminado (usa callback_prompt).
button_callback_prompt_invalid callback_prompt está en blanco o supera 2000 caracteres.
button_callback_workflow_not_found callback_workflow no es un UUID o no resuelve a un workflow activo en la organización del llamador.
button_response_invalid response falló la validación — ver detail: response_message_required, response_message_too_long, nesting_too_deep, too_many_buttons, button_too_large.
button_callback_auth_invalid callback_auth sin callback_url, tipo desconocido, campos incorrectos/extra o header_name denegado/inválido.

Los comportamientos existentes relacionados con botones (límite de buttons.length de 25, errores send_as_user_*, invalid_thread_responses, etc.) no cambian.

Secreto de firma de callback

Cada organización tiene un único secreto de firma de callback usado para HMAC. Se genera automáticamente la primera vez que se envía un mensaje con callback_url. El secreto nunca lo devuelve la API pública; obténlo del administrador de tu organización (un endpoint de rotación / recuperación está en el roadmap). Guárdalo en el almacén de secretos de tu servicio; trátalo como un secreto de cliente OAuth.

Míralo en acción

Recetas de automatización construidas sobre la API send-message, el CLI y la skill de agentes — cada una con ejemplos de curl, CLI y prompts listos para copiar y pegar.