Tu agente redacta una oferta de descuento para un cliente. El texto parece correcto, pero un envío automático podría prometer un precio equivocado o usar una versión que nadie revisó. Para este tipo de tarea, la automatización útil termina en una pausa: el agente prepara, una persona autorizada decide y solo entonces el sistema envía. La guía de Vercel sobre Chat SDK y Workflow SDK documenta cómo mantener esa espera incluso si hay un despliegue o reinicio. Es una capacidad existente, no un lanzamiento del 8 de noviembre.
Diseña primero la decisión
Supón una propuesta para el pedido 1842: un 10 % de descuento durante una semana. El agente puede buscar datos, redactar y resumir la justificación. No debe tener una herramienta de «enviar» disponible antes de la aprobación. La tarjeta que ve la persona debe mostrar destinatario, texto exacto, precio, vencimiento y una referencia al registro del pedido. Si uno de esos datos falta, el resultado es borrador pendiente, no una oferta lista para enviar.
Define cuatro resultados observables: aprobar, rechazar, caducar sin respuesta y pedir cambios. Aprobar una versión no autoriza otra. Si el agente modifica una frase después del visto bueno, la aprobación anterior deja de servir. Esta separación es más importante que el diseño visual del botón.
Cómo encaja la pausa duradera
La guía oficial usa una función marcada con "use workflow". createWebhook() ofrece una URL de reanudación; Chat SDK publica una tarjeta con botones que apuntan a esa URL. Cuando alguien pulsa, await webhook termina la espera y el flujo puede revisar la decisión. Las acciones externas viven en pasos durables marcados con "use step", que admiten reintentos. Para empezar necesitas un bot de Chat SDK, Workflow SDK, un proyecto configurado y Node.js 24 o posterior según la guía actual. pnpm add workflow instala el paquete en un proyecto que ya tiene el bot.
Para una propuesta comercial, adapta ese patrón así: preparar borrador → mostrar tarjeta → esperar aprobación → verificar identidad, versión y vigencia → enviar una sola vez → cerrar tarjeta. Agrega un tiempo límite, por ejemplo 24 horas; si vence, marca la propuesta como caducada y no envíes nada. El ejemplo de Vercel muestra cómo competir entre el webhook y un sleep("24h") durable. Al cerrar la tarjeta quita los botones para que nadie intente usar una aprobación consumida.

Una comprobación que puedes ejecutar hoy
El siguiente archivo no conecta con Slack ni envía correos. Sirve para probar una regla crítica antes de integrarla: el texto aprobado debe ser exactamente el que se enviará, la persona debe coincidir con el revisor permitido y la aprobación no debe haber vencido. Guárdalo como approval-gate.mjs y ejecútalo con node approval-gate.mjs. Los cuatro assert cubren el caso aceptado, otro usuario, un texto cambiado y una aprobación vencida:
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
const digest = (text) => createHash('sha256').update(text).digest('hex');
function maySend({ approval, authenticatedUserId, currentDraft, now }) {
if (approval.state !== 'approved') return false;
if (authenticatedUserId !== approval.approverId) return false;
if (now >= approval.expiresAt) return false;
if (digest(currentDraft) !== approval.draftHash) return false;
return true;
}
const draft = 'Offer for order 1842: 10% discount, valid this week.';
const approval = {
state: 'approved',
approverId: 'U_REVIEWER',
expiresAt: Date.parse('2026-11-09T18:00:00Z'),
draftHash: digest(draft),
};
const now = Date.parse('2026-11-08T18:00:00Z');
assert.equal(maySend({ approval, authenticatedUserId: 'U_REVIEWER', currentDraft: draft, now }), true);
assert.equal(maySend({ approval, authenticatedUserId: 'U_OTHER', currentDraft: draft, now }), false);
assert.equal(maySend({ approval, authenticatedUserId: 'U_REVIEWER', currentDraft: draft + ' Extra term.', now }), false);
assert.equal(maySend({ approval, authenticatedUserId: 'U_REVIEWER', currentDraft: draft, now: approval.expiresAt }), false);
console.log('Four approval gate checks passed.');
Esta comprobación sí se ejecutó localmente, pero no sustituye el control de identidad del servidor. En producción, authenticatedUserId debe venir de una ruta autenticada que verifique al aprobador; nunca confíes en un nombre o ID recibido sin más en el cuerpo del webhook. La guía advierte que la URL de createWebhook() se protege por su token: quien la intercepta puede intentar reanudar el flujo. Para operaciones sensibles, Vercel propone createHook() y una ruta propia autenticada que llama resumeHook(). Tampoco basta la función anterior para evitar dos envíos: el paso externo necesita una clave de idempotencia vinculada al pedido y a la versión aprobada.
Qué probar antes de confiarle clientes
Ejecuta la primera versión con datos ficticios y un buzón de prueba. Aprueba y comprueba un solo envío del texto exacto; rechaza y confirma cero envíos; deja caducar y verifica el estado; cambia una palabra después de aprobar y comprueba que el sistema pide otra autorización. Fuerza además un reintento del paso de envío para comprobar la idempotencia. Si falla cualquiera, no conectes aún la herramienta al canal real.
Para una aprobación simple que no necesita sobrevivir reinicios, un handler de Chat SDK puede bastar. Workflow SDK agrega valor cuando la espera dura horas, debe soportar despliegues o contiene varios pasos. La decisión práctica no es «poner un humano en todas partes», sino detener las acciones externas cuya equivocación cuesta más que el tiempo de revisar una tarjeta.
