Documentación
Todo lo que necesitas para integrar Illapa con tu sistema. Configura el webhook una vez y recibe el contenido generado directamente en tu CMS, base de datos o cualquier endpoint HTTP.
Integración
Integración por Webhook
Cuando un template está en modo Webhook automático, Illapa hace un POST a tu URL cada vez que se genera un post. El contenido llega estructurado y listo para insertar en tu base de datos.
POST <tu-webhook-url> Content-Type: application/json
Datos
JSON Payload
El body enviado a tu webhook siempre incluye los campos fijos más los campos personalizados que configuraste en el template.
{
"illapa_execution_id": "a1b2c3d4-5678-abcd-...",
"title": "Cómo optimizar tu blog para SEO en 2025",
"slug": "optimizar-blog-seo-2025",
"description": "Guía completa para posicionar tu blog en Google...",
"keywords": ["SEO para blogs", "posicionamiento web", "..."],
"introduction": "Párrafo introductorio con hook y keyword principal...",
"content": "<h2>Por qué el SEO importa</h2><p>Contenido...</p>",
"category_id": 5,
"author_slug": "juan",
"status": "draft"
}Referencia
Campos del payload
Campos fijos
| Campo | Tipo | Descripción |
|---|---|---|
| illapa_execution_id | string (UUID) | ID único de la ejecución. Útil para idempotencia y evitar duplicados. |
| title | string | Título del artículo generado. |
| slug | string | Slug SEO-friendly: minúsculas, guiones, sin acentos, máx. 60 caracteres. |
| description | string | Descripción SEO corta (1-2 oraciones, ~150 caracteres). |
| keywords | string[] | Array de palabras clave relevantes para el artículo. |
| introduction | string | Párrafo de introducción con la longitud configurada en el template. |
| content | string | Cuerpo completo del artículo en el formato elegido (HTML, Markdown o texto). |
| images | string[] | URLs de variantes de imagen (solo si el template tiene imágenes habilitadas). |
Campos personalizados
Los campos que configures en el template se agregan al objeto raíz junto con los campos fijos. Tipos soportados: string, number, boolean.
{
"illapa_execution_id": "...",
"title": "...",
"content": "...",
"category_id": 5,
"author_slug": "juan",
"status": "draft",
"featured": true
}Configuración
Formatos del campo "content"
Selecciona el formato en la configuración del template. El campo content llega en el formato elegido.
HTML (por defecto)
<h2>Subtítulo</h2> <p>Párrafo del artículo.</p> <ul> <li>Item de lista</li> </ul>
Markdown
## Subtítulo Párrafo del artículo. - Item de lista
Texto plano
Subtítulo Párrafo del artículo. Item de lista
Protocolo
Respuesta esperada
Illapa evalúa la respuesta de tu webhook únicamente por código HTTP. El body de tu respuesta se almacena para depuración cuando hay errores.
| Código HTTP | Resultado |
|---|---|
| 2xx (200, 201, 204…) | Ejecución marcada como entregada correctamente. |
| 4xx o 5xx | Ejecución marcada como rechazada. El body de tu respuesta se guarda para depuración. |
| Timeout / sin respuesta | Ejecución marcada como rechazada. |
Respuesta de éxito
HTTP/1.1 200 OK
{ "ok": true }Respuesta de error
HTTP/1.1 422 Unprocessable Entity
{ "error": "El campo category_id es requerido" }Implementación
Ejemplos de código
Next.js — App Router
import { NextResponse } from 'next/server'
export async function POST(req: Request) {
const {
illapa_execution_id,
title, slug, description,
keywords, introduction, content,
} = await req.json()
// Guarda el post en tu base de datos
// await db.insert(posts).values({ title, slug, content, description })
return NextResponse.json({ ok: true }, { status: 200 })
}Next.js — Pages Router
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') return res.status(405).end()
const {
illapa_execution_id,
title, slug, content, description, keywords, introduction,
} = req.body
// Tu lógica aquí
res.status(200).json({ ok: true })
}Express (Node.js)
app.post('/api/webhook-illapa', async (req, res) => {
const {
illapa_execution_id,
title, description, keywords, introduction, content,
} = req.body
// Idempotencia: verifica si ya procesaste esta ejecución
// if (await alreadyProcessed(illapa_execution_id)) {
// return res.status(200).json({ ok: true, duplicate: true })
// }
// Guarda en tu base de datos, publica en tu CMS, etc.
// await db.posts.create({ title, content })
res.status(200).json({ ok: true })
})PHP — Laravel
public function handle(Request $request)
{
$data = $request->validate([
'illapa_execution_id' => 'required|string',
'title' => 'required|string',
'slug' => 'required|string',
'description' => 'required|string',
'keywords' => 'required|array',
'introduction' => 'required|string',
'content' => 'required|string',
]);
// Post::create($data);
return response()->json(['ok' => true]);
}Python — FastAPI
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
class PostPayload(BaseModel):
illapa_execution_id: str
title: str
slug: str
description: str
keywords: List[str]
introduction: str
content: str
@app.post("/webhook-illapa")
async def receive_post(payload: PostPayload):
# Guarda en DB, etc.
return {"ok": True}Desarrollo
Pruebas locales con ngrok
Si tu servidor corre en localhost, usa ngrok para exponerlo públicamente con una URL temporal.
ngrok http 3000
https://abc123.ngrok.io. Pégala en el campo Webhook URL del template en Illapa. La URL cambia cada vez que reinicias ngrok (salvo en el plan pago).Buenas prácticas
Idempotencia
Cada request incluye illapa_execution_id — un UUID único por ejecución. Si tu webhook puede recibir el mismo post más de una vez (reintentos manuales), guarda los IDs procesados para evitar duplicados.
const existing = await db.query(
'SELECT id FROM posts WHERE execution_id = $1',
[illapa_execution_id]
)
if (existing.rows.length > 0) {
return NextResponse.json({ ok: true, duplicate: true })
}
// Procesa el post normalmente...
await db.query(
'INSERT INTO posts (execution_id, title, content) VALUES ($1, $2, $3)',
[illapa_execution_id, title, content]
)Resolución de problemas
Depuración
El webhook no recibe nada
- →Verifica que la URL sea accesible públicamente (no localhost).
- →Si estás en desarrollo, usa ngrok.
- →Revisa que el template esté en modo Webhook y verificado.
El post llega en formato incorrecto
- →Revisa el campo Formato de content en el template (HTML, Markdown, texto).
- →Los campos personalizados deben coincidir con los que espera tu backend.
- →Verifica el tab JSON Payload en la pantalla de generación.