Docs

Inicio rápido de webhooks

Conecta GrowWriter a tu propio sitio web y recibe tu primer evento de prueba firmado, en una sola página, con un solo archivo de código.

Última actualización el

Esta página te lleva de cero a Evento de prueba entregado. Un stack, un archivo, nada de teoría. Cuando la prueba funcione, la guía te enseña a guardar los artículos que vengan después, y la referencia del payload lista todo lo que contienen.

Esta página es para la persona que puede añadir un archivo a tu sitio web y desplegarlo. Si no eres tú, pásale este enlace; le lleva un solo archivo. El código es para Next.js, la base de la mayoría de los sitios en Vercel y Netlify. Los demás stacks están en la guía; los pasos de esta página no cambian.

Conecta el webhook en GrowWriter

Abre la página Publicar, busca la tarjeta Webhook en el grupo Automatización y haz clic en Conectar.

Introduce la dirección en la que responderá el código. Para el archivo de abajo es el dominio de tu sitio más /webhooks/growwriter, por ejemplo https://www.example.com/webhooks/growwriter. Usa la dirección exacta que sirve tu sitio: si redirige example.com a www.example.com, introduce la de www. Haz clic en Crear conexión.

El diálogo muestra ahora tu Secreto de firma, una cadena que empieza por whsec_. Haz clic en Copiar secreto. Solo se muestra una vez, así que deja el diálogo abierto o pégalo en un lugar seguro.

Añade el archivo receptor

Crea este archivo en tu proyecto. Lee cada petición exactamente como llegó, comprueba que GrowWriter la firmó y responde al evento de prueba.

app/webhooks/growwriter/route.ts
import { createHmac, timingSafeEqual } from 'node:crypto';

// Una petición firmada más antigua que esto se rechaza, así que una
// petición capturada no se puede repetir más tarde.
const MAX_AGE_SECONDS = 5 * 60;

function isSignedByGrowWriter(rawBody: string, header: string, secret: string) {
  // La cabecera tiene esta forma: t=1756718400,v1=5257a869e7...
  const parts = Object.fromEntries(
    header.split(',').map((part) => part.split('='))
  );
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!(age < MAX_AGE_SECONDS)) {
    return false;
  }
  // Recalcula la huella sobre "timestamp.cuerpo" con tu secreto COMPLETO
  // (prefijo whsec_ incluido) y compara en tiempo constante.
  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const provided = Buffer.from(parts.v1 ?? '', 'hex');
  const wanted = Buffer.from(expected, 'hex');
  return provided.length === wanted.length && timingSafeEqual(provided, wanted);
}

export async function POST(request: Request) {
  // Lee el cuerpo como texto primero. Parsearlo antes de comprobar la
  // firma cambia los bytes y la comprobación falla.
  const rawBody = await request.text();
  const signature = request.headers.get('x-growwriter-signature') ?? '';
  const secret = process.env.GROWWRITER_WEBHOOK_SECRET ?? '';

  if (!isSignedByGrowWriter(rawBody, signature, secret)) {
    return new Response('Invalid signature', { status: 401 });
  }

  const payload = JSON.parse(rawBody);

  // "Enviar evento de prueba" manda un ping sin ningún artículo dentro.
  // Responder 200 es lo que hace que la prueba tenga éxito.
  if (payload.event === 'ping') {
    return new Response('pong');
  }

  // Un artículo real (o su eliminación: article.unpublished solo lleva el
  // id). Por ahora, solo demuestra que llegó; la guía enseña a guardarlo.
  // Cuando lo guardes, Response.json({ url }) con la URL pública de la
  // entrada le da al diálogo de publicación un enlace "Ver publicación".
  console.log(`${payload.event}: ${payload.article.id}`);
  return new Response('OK');
}

Dale el secreto a tu sitio y despliega

Tu código lee el secreto de una variable de entorno: un ajuste que tu proveedor de hosting guarda fuera de tu código. En el panel de tu host (Vercel: Settings, luego Environment Variables) añade:

Variable de entorno
GROWWRITER_WEBHOOK_SECRET=whsec_paste_your_secret_here

Pega el valor completo, whsec_ incluido. Después despliega tu sitio para que el archivo nuevo y la variable nueva estén en producción.

Envía el evento de prueba

De vuelta en el diálogo de GrowWriter, haz clic en Enviar evento de prueba. GrowWriter firma un pequeño ping y lo envía a tu dirección. Cuando tu archivo responde, el diálogo muestra Evento de prueba entregado. Haz clic en Hecho.

Si falla, el diálogo indica el motivo. Los dos que más probablemente verás:

  • La URL de tu endpoint redirige. Introduce la dirección a la que redirige e inténtalo de nuevo: tu sitio responde en otra dirección (normalmente www frente a sin www), y el mensaje muestra cuál usar. Haz clic en Editar URL en la fila del webhook para cambiarla.
  • Tu endpoint rechazó la entrega: con un 401, el secreto de tu host no coincide; con un 404, el archivo no está en la dirección que introdujiste.

Todo lo demás está en la página de solución de problemas.

Demuestra que la comprobación funciona

Opcional, pero merece un minuto. Este script firma un ping exactamente como lo hace GrowWriter y lo envía a tu sitio:

send-test-ping.sh
SECRET="whsec_paste_your_secret_here"
URL="https://www.example.com/webhooks/growwriter"
BODY='{"version":"2026-09-06","event":"ping","deliveryId":"9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e","sentAt":"2026-09-01T09:30:00.000Z"}'

TIMESTAMP=$(date +%s)
SIGNATURE=$(printf '%s.%s' "$TIMESTAMP" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')

curl -X POST "$URL" \
  -H "Content-Type: application/json" \
  -H "X-GrowWriter-Event: ping" \
  -H "X-GrowWriter-Delivery: 9d2f7c1e-6a3b-4c8d-b5e0-1f2a3b4c5d6e" \
  -H "X-GrowWriter-Signature: t=$TIMESTAMP,v1=$SIGNATURE" \
  --data "$BODY"

Deberías recibir pong. Ahora cambia un carácter dentro de BODY y ejecútalo otra vez: deberías recibir un 401. Esa es tu comprobación de firma rechazando una petición manipulada, que es justo de lo que se trata.

Qué tienes ahora

Un endpoint público que solo acepta peticiones firmadas por GrowWriter, y una conexión que aparece como Conectado en la página Canales. Publicar un artículo lo entregará a este archivo como un evento article.published, y la línea console.log mostrará su id en los logs de tu host.

Siguiente paso: guarda el artículo

El archivo de arriba solo registra el artículo en el log. Para almacenarlo, lee Guarda el artículo en tu CMS en la guía: seis reglas cortas sobre ids, fechas, estado de publicación, palabras clave, imágenes y escapado, cada una aprendida de un receptor real que se equivocó en ellas. Los ejemplos completos de receptor que hay allí aplican las seis.

¿Te preguntas por qué existen la firma y el secreto? La guía lo explica en cuatro pasos sencillos.

En esta página