Solución de problemas de webhooks
Arregla un evento de prueba o una entrega fallidos por síntoma, con los mensajes exactos que muestra GrowWriter, qué significa cada uno y qué hacer.
Última actualización el
Cada entrada de abajo empieza por lo que ves, dice qué significa en palabras llanas y termina con la solución. Empieza por la primera entrada: casi todos los problemas de una primera conexión se resuelven encontrando el código de estado real.
Primero, encuentra el código de estado real
Qué hacer. Envía una petición de prueba sin firmar a tu endpoint desde tu
propio ordenador y mira solo la línea de estado y la cabecera Location:
curl -sS -o /dev/null -D - -X POST \
-H "Content-Type: application/json" -d '{}' \
https://your-site.com/webhooks/growwriter | grep -iE "^(HTTP|Location)"Cómo leer el resultado. La petición no va firmada, así que un 401 es la
respuesta buena: tu endpoint está activo y rechaza las peticiones sin firmar,
exactamente como debe.
| Recibes | Significa |
|---|---|
HTTP … 401 o 400 | Bien. El endpoint funciona y comprueba las firmas. Las peticiones firmadas funcionarán. |
HTTP … 3xx + Location: | Tu URL redirige. Usa la dirección de Location: en su lugar. Mira redirecciones. |
HTTP … 404 | Ruta equivocada, o la ruta no está desplegada. |
HTTP … 405 | La ruta existe pero no acepta POST. |
HTTP … 500 | Tu código falló. Normalmente falta una variable de entorno (el secreto). |
| Error de conexión / timeout | DNS, firewall, o el sitio no es público. |
Si recibes un 3xx, ejecuta el comando otra vez con la dirección de
Location: hasta que recibas un 4xx. Esa dirección final es la que debes
introducir en GrowWriter.
El evento de prueba falla con HTTP 301, 302, 303, 307 o 308
Qué ves. El diálogo muestra La URL de tu endpoint redirige. Introduce la dirección a la que redirige e inténtalo de nuevo, con el destino de la redirección en la línea de abajo.
Qué significa. Tu sitio respondió "ve allí en vez de aquí". Los navegadores lo siguen en silencio, así que tu sitio se ve bien cuando lo abres. GrowWriter nunca sigue una redirección: seguirla podría perder el cuerpo de la petición o romper la firma. Así que una redirección siempre cuenta como entrega fallida, y los tres intentos van a la URL exacta que introdujiste.
Las causas habituales:
| Causa | Redirección típica |
|---|---|
example.com frente a www.example.com | Tu host redirige uno al otro. Usa aquel al que redirige. |
http:// en lugar de https:// | http://… → https://… |
| Una barra final que tu framework añade o quita | /webhook → /webhook/ o al revés |
| Un prefijo de idioma que tu framework añade | /api/webhook → /en/api/webhook |
| Una URL de previsualización que se reasignó | *.vercel.app → tu dominio de producción |
| Una regla de CDN o proxy ("Always use HTTPS", reglas de página) | cualquiera de las anteriores |
Solución. Copia la dirección del error (o de la cabecera Location: en
la comprobación de arriba) e introdúcela como URL del
endpoint. Si el diálogo de conexión sigue abierto, haz clic en Volver; si
ya lo cerraste, haz clic en Editar URL en la fila del webhook de la página
Canales. Las dos opciones conservan tu secreto de firma. Desconectar y volver
a conectar, en cambio, crea un secreto nuevo que tendrías que actualizar en tu
servidor.
"Tu endpoint no respondió a tiempo"
Qué significa. GrowWriter esperó 10 segundos una respuesta y se rindió. Cada uno de los tres intentos tiene sus propios 10 segundos.
Solución. Responde antes de hacer el trabajo lento. La guía describe dos
patrones seguros: mantén el handler seguro de ejecutar dos veces y deja que
un reintento termine el trabajo, o responde 200 primero y haz el trabajo
pesado en un proceso en segundo plano. Mira
¿Y el límite de 10 segundos?.
"Tu endpoint rechazó la entrega"
Qué significa. Tu endpoint respondió con un estado que no es 2xx. El
mensaje cita ese estado y los primeros 200 caracteres del cuerpo de tu
respuesta, así que devuelve un motivo corto en texto plano desde tu handler
y lo leerás directamente en el diálogo. El estado te dice dónde mirar:
401o403: tu comprobación de firma dijo que no. Mira la firma nunca coincide.404: ruta equivocada. Compara la URL de GrowWriter con tu ruta.405: la ruta no aceptaPOST.413: tu servidor rechaza cuerpos tan grandes. Un artículo completo puede ocupar varios cientos de kilobytes. Mira payloads grandes.500: tu código falló. Revisa los logs de tu servidor; la causa más común es que falte la variable de entorno del secreto.
Falta "Ver publicación" o abre la página equivocada
Qué significa. El enlace es la URL que devolvió tu endpoint en el cuerpo
de su respuesta 2xx, como {"url": "https://…"}. Responde con cualquier
otra cosa y no hay enlace. GrowWriter no tiene otra forma de saber dónde
puso tu sitio la entrada.
Solución. Devuelve la URL pública de la entrada desde tu handler una vez
guardada la entrada. Tiene que ser una URL absoluta http o https; las
rutas relativas y otros esquemas se ignoran. Mira
La respuesta para
conocer las reglas. Dos cosas que debes saber:
- Si respondes
200antes de crear la entrada (el patrón del trabajo en segundo plano), todavía no puedes saber su URL, así que el diálogo no muestra ningún enlace. - El enlace guardado se reemplaza en cada entrega exitosa. Si un enlace apunta a una dirección antigua, publica el artículo otra vez y la respuesta actual de tu endpoint lo reemplaza.
"La entrega falló tras varios intentos"
Qué significa. GrowWriter no pudo conectar con tu endpoint en ninguno de los tres intentos: falló el DNS, la conexión fue rechazada o el sitio no es accesible desde internet.
Solución. Comprueba que la URL está activa, usa https:// y es pública.
Las direcciones de redes privadas y localhost están bloqueadas por
seguridad. Para recibir eventos reales en tu máquina mientras desarrollas,
usa un servicio de túnel (ngrok, cloudflared) e introduce la dirección HTTPS
del túnel.
La URL se rechaza al conectar
GrowWriter comprueba la URL antes de guardarla. Los tres mensajes y su significado:
- La URL del webhook debe usar HTTPS: solo se acepta
https://. - La URL del webhook apunta a una dirección privada y no se puede usar:
el nombre de host resuelve a una dirección de red local o privada
(
localhost,10.x,192.168.xy similares). Usa una dirección pública o un túnel. - La URL del webhook no es válida: la URL no se puede parsear, contiene un usuario o una contraseña, o su nombre de host no se puede resolver.
GrowWriter no comprueba si el endpoint responde en este punto. Para eso está Enviar evento de prueba.
La firma nunca coincide
Repasa esta lista en orden. Cada punto explica una clase entera de fallos:
- Verifica sobre el cuerpo sin procesar. Calcula la firma sobre los bytes exactos que recibiste, antes de cualquier parseo de JSON. Parsear y volver a serializar cambia los bytes.
- Usa el secreto completo. La clave es la cadena entera, prefijo
whsec_incluido. - Comprueba la variable de entorno. Mismo nombre en tu código y en tu hosting, definida para el entorno que sirve el endpoint, y con un nuevo despliegue después de definirla.
- Comprueba la ventana de cinco minutos. Si el reloj de tu servidor va desfasado más de cinco minutos, todas las firmas parecen caducadas. Sincroniza el reloj.
- Compara el formato de la cabecera.
X-GrowWriter-Signatureest=<segundos unix>,v1=<64 caracteres hex>, y la cadena firmada es"<t>.<cuerpo sin procesar>".
La prueba en local firma una petición igual que GrowWriter, así que puedes confirmar tu comprobación sin esperar una entrega real.
La prueba funciona pero los artículos reales fallan
Qué significa. El ping es diminuto. Un artículo real lleva su contenido
tres veces (markdown, HTML y bloques) y suele ocupar unos cientos de
kilobytes; los artículos largos pueden superar 1 MB. Los servidores y los
frameworks suelen limitar los cuerpos JSON muy por debajo de eso.
Solución. Sube el límite de tamaño del cuerpo en la ruta (por ejemplo
express.raw({ type: 'application/json', limit: '10mb' }); el valor por
defecto de Express es 100 KB) y asegúrate de que tu lógica de guardado
maneja el payload completo, no solo los campos que usó la prueba.
La prueba funciona, pero el primer artículo falla con un 500 en Vercel o Netlify
Qué significa. Tu handler escribe un archivo junto a tu código. Los hosts
serverless montan ese directorio en solo lectura, así que la escritura lanza
un error (EROFS) y el endpoint responde 500. La prueba funciona porque el
ping responde antes de que ocurra ningún guardado.
Solución. Guarda el artículo en una base de datos, en la API de un CMS o
en un almacenamiento de objetos en lugar de en un archivo local. Los ejemplos
de Next.js y Express de la guía registran la entrada en el log exactamente
por esta razón; sustituye la línea del log por tu llamada de almacenamiento.
Si solo necesitas un archivo temporal, /tmp es escribible pero se vacía
entre invocaciones.
Perdí mi secreto de firma
Qué significa. El secreto se muestra una sola vez, en el diálogo de conexión, y se guarda cifrado. GrowWriter no puede mostrarlo de nuevo.
Solución. Desconecta el webhook en la página Canales y vuelve a conectarlo. Eso crea un secreto nuevo y retira el anterior: actualiza la variable de entorno en tu servidor, vuelve a desplegar y después envía un evento de prueba.
El artículo está en línea en mi sitio, pero el panel de mi CMS muestra un error de validación
Qué ves. Algo como Key on object value "growwriterId" is not allowed,
o un valor de fecha rechazado.
Qué significa. Tu endpoint guardó un campo que tu CMS no declara (normalmente el id de GrowWriter), o un valor en un formato que el campo no acepta (normalmente un timestamp completo en un campo de fecha sin hora).
Solución. Añade un campo propio para el id a tu modelo de contenido y convierte las fechas al formato que espera tu CMS. Las dos cosas se explican en Guarda el artículo en tu CMS.
Una entrada volvió a borrador sin avisar
Qué significa. El payload no tiene indicador de publicado. Si tu CMS trata un campo de borrador ausente como "borrador", la entrada solo está publicada hasta que alguien la guarda en el panel de administración.
Solución. Establece tú mismo el estado de publicación, de forma
explícita, cuando gestiones article.published y article.updated. Mira la
regla 3.
Mi build falla por un { o un < dentro del artículo
Qué significa. El texto del artículo contiene caracteres normales como
{, } y <. Si compilas el markdown con MDX, JSX o un motor de
plantillas, esos caracteres se leen como código.
Solución. Guarda el markdown como datos y renderízalo con un
renderizador de Markdown, o escapa esos caracteres antes de compilar.
content.html ya viene escapado y es seguro para insertarlo directamente.
Mira la regla 6.
Las imágenes son enormes, faltan o sus URLs cambiaron
- Enormes: las imágenes llegan a su resolución original y pueden superar los 10 MB. Redimensiónalas y comprímelas al descargarlas.
- Faltan:
coverImagese omite cuando el artículo no tiene portada;imageses un array vacío cuando no hay imágenes interiores. Comprueba que la clave existe antes de leerla. - URLs cambiadas: una imagen sustituida en el artículo recibe una URL nueva en la siguiente entrega. Las imágenes que no cambian conservan sus URLs.
Las tres cosas se tratan en la regla 5.
Recibí el mismo artículo dos veces
- Mismo id de entrega: un reintento. Tu endpoint respondió tarde o con un error en el primer intento. Sáltate el trabajo que ya hiciste para ese id.
- Id de entrega distinto,
article.publishedotra vez: el artículo se publicó en una segunda conexión de webhook, o la primera conexión aún no tenía ninguna entrega exitosa. Haz upsert porarticle.idy la segunda copia sobrescribe la primera.
Eliminé un artículo en GrowWriter, pero sigue en mi sitio
Qué significa. Eliminar un artículo (o desconectar el webhook) envía
article.unpublished con el id, el slug y el language del artículo, y
nada más. Si tu handler trata todo evento que no es un ping como un artículo
que guardar, o bien ignoró el evento o bien intentó guardar un artículo sin
contenido y respondió 500.
Solución. Gestiona article.unpublished antes del guardado: busca la
entrada por article.id y después ocúltala o elimínala, lo que prefiera tu
sitio. Todos los ejemplos de receptor de la guía tienen esa rama. El evento
sigue las reglas de entrega normales (tres intentos y después nada), así que
si tu endpoint estaba caído en ese momento la entrada se queda; GrowWriter no
reintenta más tarde.
El slug contiene caracteres no latinos, o no cambió con el título
El slug se deriva del título una sola vez, cuando se genera el brief. Los
acentos latinos se eliminan (qué → que), pero las letras de otros
alfabetos se conservan, así que un título en japonés produce un slug en
japonés: codifícalo con porcentajes antes de ponerlo en una URL. Nunca está
vacío. Editar el título en el editor conserva el slug, así que la dirección
de la entrada se mantiene estable; solo elegir un título nuevo en el writer
produce un slug nuevo. Nunca uses el slug como nombre de archivo ni como
clave; usa article.id y trata el slug como dato de presentación.
"Demasiadas solicitudes. Inténtalo más tarde"
Qué significa. Tu cuenta alcanzó un límite: 20 solicitudes de publicación por hora, o 60 eventos de prueba por hora. No se pierde nada. El artículo sigue en tu Biblioteca y la conexión queda como estaba.
Solución. Espera unos minutos y vuelve a publicar o a enviar el evento de prueba. Si publicas en varios canales a la vez, cada uno cuenta como una solicitud.
La fila del webhook dice "Sin verificar"
Qué significa. El endpoint nunca ha respondido a un evento de prueba ni a una publicación. GrowWriter comprueba la dirección al conectar, pero no la llama hasta que tú lo haces.
Solución. Haz clic en Enviar evento de prueba en la fila del webhook. Cuando tenga éxito, la fila pasa a Conectado. Si falla, empieza por la comprobación del estado y sigue con redirecciones o entrega rechazada. Publicar en un webhook Sin verificar está permitido, y la primera entrega con éxito también lo verifica.
¿Sigues atascado?
Escríbenos desde la aplicación e incluye: el id de entrega (de la cabecera
X-GrowWriter-Delivery o del error), la hora del intento, el estado que
devolvió tu servidor y la salida de
la comprobación del estado. Con esas cuatro cosas
normalmente podemos responder en un solo mensaje.