Cuándo usar webhook y cuándo usar API
El webhook es la vía recomendada si tu web tiene backend propio y puede exponer un endpoint público: PosicionaIA envía el artículo en cuanto se aprueba (o automáticamente si tienes activado el modo autopilot), sin que tengas que consultar nada.
La API REST y el servidor MCP son la alternativa cuando prefieres tirar tú de los datos: por ejemplo para integrar la publicación dentro del pipeline de build de un sitio estático, o para sincronizar auditorías y keywords con tu propio panel.
- Webhook: entrega push en tiempo real al aprobar o al generar en automático
- API: consulta pull de sitios, auditorías, keywords y publicaciones
- Compatible con cualquier lenguaje de backend: PHP, Node, Python, Go, .NET…
Endpoint y autenticación
Enviamos siempre una petición POST sobre HTTPS con la cabecera Content-Type: application/json. Si has guardado una API key en el panel, la incluimos además en la cabecera Authorization con el formato «Bearer {tu API key}». No usamos firmas HMAC: la autenticación es por API key en cabecera, comparada en tiempo constante en tu lado.
Tu endpoint debería rechazar cualquier método distinto de POST con un 405, responder 401 si falta la cabecera Authorization y 403 si la clave no coincide. En PHP compara siempre con hash_equals(); en Node con crypto.timingSafeEqual(), nunca con un == directo.
- Método: POST únicamente · Protocolo: HTTPS obligatorio
- Cabeceras: Content-Type: application/json y Authorization: Bearer {API key}
- Sin cabecera Authorization → 401 · clave incorrecta → 403 · método distinto → 405
- Guarda la API key fuera del docroot (fichero de configuración o variable de entorno)
Payload JSON que enviamos
El cuerpo de la petición es un objeto JSON plano, codificado en UTF-8. Los campos obligatorios son title, body (con html como alias del mismo valor), slug y status; el resto son opcionales pero recomendables para el SEO de la ficha.
Estructura completa: site (URL del sitio), cms ("webhook"), title (string, se usa como H1 y meta title), body y html (string con el HTML del artículo, mismo valor en ambos campos por compatibilidad), slug (minúsculas, números y guiones), status ("publish" o "draft"), metaDescription (máximo 160 caracteres) y publishedAt (fecha ISO 8601 en UTC).
El slug es la clave de idempotencia: si recibes un slug que ya existe, sobrescribe el artículo en lugar de crear un duplicado. Así los reintentos nunca generan contenido repetido.
- Obligatorios: title, body/html, slug, status
- Opcionales: site, cms, metaDescription, publishedAt
- status: "draft" debe crear el contenido sin publicarlo
- El slug identifica de forma única el artículo: úsalo como clave idempotente
Especificación del HTML que recibes
El HTML llega ya normalizado, saneado y validado por nosotros: puedes insertarlo tal cual dentro de tu plantilla. Es HTML semántico sin clases CSS, para que puedas aplicarle las de tu propio tema.
Estructura típica: encabezados h2 y h3, párrafos, listas ul/ol, tablas con thead y tbody, blockquote para destacados, un bloque de «Preguntas frecuentes» y otro de «Fuentes y referencias» con enlaces externos. Las etiquetas empleadas son h2, h3, p, ul, ol, li, table, strong, b, em, i, a, blockquote, img y div.
Nunca incluimos el h1: genéralo tú desde el campo title, para evitar duplicados. Tampoco enviamos etiquetas html, head, body, script ni style. Las imágenes llegan con src absoluto en https y atributo alt, y los enlaces externos con rel="noopener nofollow" y target="_blank".
- HTML semántico sin clases: aplica las de tu tema al insertarlo
- No incluye h1 ni etiquetas de documento (html, head, body, script, style)
- Imágenes con src absoluto y alt; enlaces externos con rel="noopener nofollow"
- Codificación UTF-8 obligatoria: acentos, ñ y comillas angulares
Adaptar el HTML al CSS de tu tema
Como el HTML no lleva clases, lo habitual es pasarlo por una función de formateo antes de insertarlo en la plantilla: recorre las etiquetas y añade las clases de tu diseño (por ejemplo una clase para las listas, otra para las tablas y otra para los bloques destacados), envuelve las tablas en un contenedor con scroll horizontal para móvil y añade el atributo loading="lazy" a las imágenes.
En PHP suele resolverse con una función format_article_body() basada en DOMDocument o en reemplazos con expresiones regulares sobre las etiquetas de apertura; en JavaScript, parseando el HTML con DOMParser y recorriendo el árbol. En el panel, dentro de la ficha del sitio, tienes el ejemplo completo listo para copiar.
- Añade tus clases a ul/ol, table, blockquote y figure al insertar
- Envuelve las tablas en un div con overflow-x para que respondan en móvil
- Añade loading="lazy" y decoding="async" a las imágenes del cuerpo
- Genera los anclas de los h2 si quieres índice de contenidos propio
Automatizar listado, sitemap y datos estructurados
Cuando tu endpoint guarda un artículo nuevo conviene que actualice también el listado del blog, el sitemap.xml y el feed si lo tienes: así Google y los rastreadores de IA descubren la publicación sin esperar al siguiente rastreo completo.
Aprovecha metaDescription, publishedAt y la URL canónica para rellenar las etiquetas SEO y el Schema.org de tipo Article (headline, datePublished, dateModified, author, publisher e image). Si el artículo trae bloque de preguntas frecuentes, genera además el FAQPage correspondiente a partir de los pares h3/p de esa sección.
- Regenera el índice del blog y el sitemap.xml al recibir cada publicación
- Vuelca metaDescription y publishedAt en las meta y en el JSON-LD Article
- Genera FAQPage a partir del bloque de «Preguntas frecuentes»
- Declara la URL canónica del artículo para evitar duplicados
Respuesta esperada
Responde con un 200 o un 201 y un JSON que incluya la URL final del artículo: es lo que nos permite mostrar el enlace «Ver publicación» en el panel. El objeto ideal contiene url, status, slug y publishedAt.
También aceptamos otras formas habituales: un campo link, permalink, un objeto post con url dentro, o incluso una respuesta en texto plano que sea la URL. Si no devuelves ninguna URL, la deducimos como {site}/blog/{slug}, lo que puede no coincidir con tu estructura real.
- Código 200 o 201 con Content-Type: application/json
- Campos recomendados: url, status, slug, publishedAt
- Alternativas aceptadas: link, permalink, post.url o la URL en texto plano
- Tiempo de respuesta por debajo de 15 s y cuerpo admitido de al menos 1 MB
Códigos de error y diagnóstico
Devuelve siempre JSON, también en los errores: el panel muestra el código y los primeros 300 caracteres del cuerpo, así que un mensaje claro ahorra mucho tiempo de depuración. Si respondes HTML de error, la incidencia será mucho más difícil de interpretar.
Convención recomendada: 400 con «Missing required field: {campo}» cuando falte un obligatorio, 401 si falta o es inválida la cabecera Authorization, 403 si la API key no coincide, 405 si el método no es POST y 500 para errores internos de tu plantilla o base de datos. En el panel, un 401 o 403 se muestra como «sin acceso»: casi siempre significa que la API key guardada no es la que valida tu servidor.
- 400 Bad Request → falta un campo obligatorio
- 401 Unauthorized → falta la cabecera Authorization
- 403 Forbidden → API key incorrecta
- 405 Method Not Allowed → método distinto de POST
- 500 Server Error → fallo interno de tu endpoint o plantilla
Ejemplos de implementación
En el panel, dentro de la ficha del sitio, encontrarás la documentación interactiva con diez bloques copiables: endpoint y autenticación, payload JSON comentado, especificación del HTML, adaptación al CSS del tema, automatización de listado y sitemap, respuesta esperada, códigos de error, ejemplo cURL, implementación completa en PHP (blog-webhook.php) y cliente de ejemplo en JavaScript.
El ejemplo en PHP cubre el flujo completo: rechazo de métodos no permitidos, verificación de la API key con hash_equals(), validación de campos obligatorios, saneado del slug, cálculo de la meta description a partir del texto cuando no llega, renderizado de la plantilla y respuesta 201 con la URL final.
- Ejemplo cURL para probar tu endpoint antes de conectarlo
- blog-webhook.php completo y comentado, listo para adaptar
- Cliente JavaScript equivalente para backends Node
- Checklist final de buenas prácticas antes de dar por cerrada la integración
Prompt para que tu IA genere el webhook y la API
Si prefieres no escribir el endpoint a mano, copia el siguiente prompt y pégalo en ChatGPT, Claude, Cursor, Lovable o el asistente que uses. Incluye la especificación completa: autenticación Bearer, payload campo a campo, tratamiento del HTML, formateo al CSS de tu tema, actualización de listado y sitemap, datos estructurados, respuesta esperada, códigos de error, la API de lectura y los entregables que debe producir.
Antes de enviarlo, sustituye los tres campos entre corchetes: tu lenguaje o framework, tu dominio y el sistema donde guardas el contenido. Cuanto más concreto seas, menos ajustes necesitarás después.
Prompt completo · webhook + API de PosicionaIA
Actúa como desarrollador backend senior. Necesito que crees, completo y funcional, el endpoint que recibirá las publicaciones que me envía PosicionaIA (plataforma de SEO/GEO automático de SAGATECH) y una pequeña API de lectura para gestionarlas. Mi stack es: [INDICA AQUÍ tu lenguaje/framework: PHP plano, WordPress, Laravel, Node/Express, Next.js, Python/FastAPI, .NET…] y mi web está en [INDICA AQUÍ tu dominio]. El contenido se guarda en [INDICA AQUÍ: MySQL, PostgreSQL, ficheros Markdown, CMS propio…].
=== 1. ENDPOINT WEBHOOK (recepción de publicaciones) ===
Ruta sugerida: POST https://midominio.com/blog-webhook.php (adáptala a mi stack).
Requisitos obligatorios:
- Solo método POST sobre HTTPS. Cualquier otro método responde 405 con {"error":"Method not allowed"}.
- Content-Type: application/json. Lee el cuerpo crudo y decodifícalo como UTF-8.
- Autenticación por API key en la cabecera "Authorization: Bearer {API_KEY}".
· Sin cabecera Authorization -> 401 {"error":"Missing or invalid Authorization header"}.
· Key incorrecta -> 403 {"error":"Invalid API key"}.
· Compara SIEMPRE en tiempo constante (hash_equals en PHP, crypto.timingSafeEqual en Node).
· La API key se lee de variable de entorno o de un fichero de configuración FUERA del docroot. Nunca hardcodeada.
· En Apache/PHP añade la regla de .htaccess necesaria para que la cabecera Authorization llegue al script.
=== 2. PAYLOAD JSON QUE RECIBIRÉ ===
{
"site": "https://midominio.com", // string URL del sitio
"cms": "webhook", // string plataforma configurada
"title": "Marketing digital para pymes", // string OBLIGATORIO · se usa como H1 y meta title
"body": "<h2>Introducción</h2><p>…</p>", // string OBLIGATORIO · HTML del artículo
"html": "<h2>Introducción</h2><p>…</p>", // string alias de body, mismo valor
"slug": "marketing-digital-pymes", // string OBLIGATORIO · minúsculas, números y guiones
"status": "publish", // string OBLIGATORIO · "publish" | "draft"
"metaDescription": "Guía de marketing…", // string opcional · máx. 160 caracteres
"publishedAt": "2026-07-29T10:00:00.000Z" // string opcional · ISO 8601 en UTC
}
Validación: si falta title, body/html, slug o status -> 400 {"error":"Missing required field: {campo}"}.
Sanea el slug a [a-z0-9-]. Si metaDescription no viene, genérala con los primeros 160 caracteres del texto sin etiquetas. Si publishedAt no viene, usa la fecha actual en UTC.
IDEMPOTENCIA OBLIGATORIA: el slug es la clave única. Si ya existe un artículo con ese slug, ACTUALÍZALO en lugar de crear un duplicado (UPSERT). Los reintentos nunca deben duplicar contenido.
=== 3. HTML QUE RECIBIRÉ Y CÓMO TRATARLO ===
El HTML llega ya saneado y validado, semántico y SIN clases CSS. Etiquetas usadas: h2, h3, p, ul, ol, li, table (con thead/tbody), strong, b, em, i, a, blockquote, img, div.
Reglas que debes respetar en el código:
1. NO incluye <h1>: genéralo tú desde el campo "title".
2. NO incluye <html>, <head>, <body>, <script> ni <style>. Insértalo dentro de mi plantilla.
3. Las imágenes llegan con src absoluto https y atributo alt.
4. Los enlaces externos llevan rel="noopener nofollow" target="_blank".
5. Codificación UTF-8 (ñ, á, é, ü, «»). Configura la conexión a base de datos como utf8mb4.
Escribe además una función format_article_body($html) (o su equivalente en mi lenguaje) que, antes de insertar el HTML en la plantilla:
- Añada las clases CSS de mi tema a las etiquetas: [INDICA AQUÍ tus clases, p. ej. ul -> "artul", table -> "arttbl", blockquote -> "artcall"].
- Envuelva cada <table> en <div class="arttblwrap"> con overflow-x auto para que responda en móvil.
- Añada loading="lazy" y decoding="async" a las <img> del cuerpo.
- Genere un id slugificado en cada <h2>/<h3> y construya un índice de contenidos con enlaces ancla al principio del artículo.
Usa un parser de DOM (DOMDocument en PHP, DOMParser/cheerio en JS) en vez de expresiones regulares frágiles, y preserva el UTF-8.
=== 4. LO QUE DEBE HACER AL GUARDAR ===
- Guardar el artículo con: title, slug, html formateado, metaDescription, status y publishedAt.
- status "draft" crea el contenido SIN publicarlo; "publish" lo hace visible.
- Regenerar/actualizar automáticamente el listado del blog (índice paginado) al recibir cada publicación.
- Actualizar automáticamente sitemap.xml añadiendo la URL con <lastmod>, y el feed RSS si existe.
- Renderizar en el <head> del artículo: <title>, meta description, canonical, Open Graph y Twitter Card.
- Inyectar JSON-LD Schema.org de tipo Article con headline, description, datePublished, dateModified, author, publisher (con logo) e image cuando exista.
- Si el HTML contiene un bloque <h2>Preguntas frecuentes</h2>, extraer los pares h3/p siguientes y generar además un JSON-LD de tipo FAQPage.
- Registrar cada recepción en un log (fecha, slug, status, resultado) para poder depurar.
=== 5. RESPUESTA QUE DEBE DEVOLVER ===
HTTP 201 (o 200 si actualiza) con Content-Type: application/json y este cuerpo:
{
"url": "https://midominio.com/blog/{slug}",
"status": "publish",
"slug": "{slug}",
"publishedAt": "2026-07-29T10:00:00.000Z"
}
La URL es imprescindible: PosicionaIA la muestra como enlace «Ver publicación».
Devuelve SIEMPRE JSON, también en los errores (nunca HTML de error), con el formato {"error":"mensaje"}.
Tiempo de respuesta por debajo de 15 segundos y admisión de cuerpos de al menos 1 MB.
Códigos: 400 campo obligatorio ausente · 401 falta Authorization · 403 API key inválida · 405 método no permitido · 500 error interno.
=== 6. API DE LECTURA Y GESTIÓN (además del webhook) ===
Crea estos endpoints, protegidos con la misma API key Bearer:
- GET /api/articles?page=1&limit=20&status=publish -> listado paginado con id, title, slug, status, publishedAt, url.
- GET /api/articles/{slug} -> artículo completo con html y metadatos.
- PATCH /api/articles/{slug} {"status":"publish"} -> cambia draft <-> publish.
- DELETE /api/articles/{slug} -> elimina el artículo y lo saca del sitemap.
- GET /api/health -> {"ok":true,"version":"1.0"} sin autenticación, para comprobar que el endpoint está vivo.
Todas las respuestas en JSON, con paginación en la forma {"items":[…],"page":1,"total":123}.
=== 7. SEGURIDAD Y ROBUSTEZ ===
- Rechaza peticiones que no lleguen por HTTPS.
- Limita el tamaño del cuerpo aceptado y valida tipos de todos los campos.
- Escapa el título y la meta description al renderizar (evita XSS); el cuerpo HTML confía en el saneado de origen pero puedes pasarlo por una lista blanca de etiquetas si quieres doble seguridad.
- Usa consultas preparadas para todo acceso a base de datos.
- No expongas trazas ni rutas internas en los mensajes de error.
- Soporta la rotación de API key aceptando dos claves válidas simultáneamente durante la transición.
=== 8. ENTREGABLES ===
1. El código completo del endpoint webhook, comentado y listo para subir.
2. El código de los endpoints de la API de lectura.
3. El esquema SQL (o el modelo de datos) de la tabla de artículos, con índice único por slug.
4. La función de formateo del HTML al CSS de mi tema.
5. La plantilla de artículo con head SEO y JSON-LD.
6. Un comando cURL de prueba que simule una publicación real de PosicionaIA.
7. Un README breve: dónde poner la API key, cómo probarlo y qué URL debo pegar en «Modificar credenciales» dentro de PosicionaIA.
Genera todo el código, sin omitir partes con comentarios del tipo «aquí iría…». Si necesitas alguna decisión que no te he dado, elige la opción más estándar y explícala en una línea.Servidor MCP para agentes
Además del webhook y la API REST, PosicionaIA expone un servidor MCP (Model Context Protocol). Desde tu perfil en el panel encontrarás la URL del endpoint y el fragmento de configuración para usarlo con agentes compatibles como Claude, Cursor u otros clientes MCP, accediendo a keywords, auditorías e inteligencia de dominio directamente desde tu flujo de trabajo.
Errores frecuentes y comprobaciones
Antes de dar por fallida una integración a medida, revisa estos puntos, responsables de la mayoría de incidencias:
- El endpoint debe ser accesible públicamente por HTTPS, no solo desde tu red interna, y con certificado válido
- Si tu servidor redirige (http→https o con/sin www), asegúrate de que la redirección conserva el método POST y el cuerpo
- Comprueba que la API key guardada en el panel es exactamente la que valida tu endpoint, sin espacios ni saltos de línea
- Algunos hostings no exponen la cabecera Authorization en PHP: activa la regla correspondiente en .htaccess si llega vacía
- Controla la idempotencia por slug: si tu endpoint tarda demasiado y reintentamos, no debe duplicarse el artículo
- Devuelve JSON también en los errores para poder leer la causa desde el panel
Preguntas frecuentes
- ¿Usáis firma HMAC?
- No. La autenticación se hace con una API key que enviamos en la cabecera Authorization: Bearer. Compárala en tu servidor con una función de comparación en tiempo constante como hash_equals() o crypto.timingSafeEqual().
- ¿Por qué llegan body y html con el mismo contenido?
- Por compatibilidad con distintas implementaciones: algunos endpoints esperan «body» y otros «html». Usa el que prefieras, el valor es idéntico.
- ¿El HTML incluye el título como h1?
- No. El cuerpo empieza directamente en h2 para que generes el h1 desde el campo title y evites duplicar encabezados de primer nivel.
- ¿Puedo usar webhook y API a la vez?
- Sí, muchos equipos usan el webhook para recibir el contenido en tiempo real y la API o el MCP para auditorías y para reconciliar datos periódicamente.
- ¿Qué pasa si mi endpoint está caído cuando se envía el contenido?
- La entrega falla y la incidencia queda registrada en la ficha del sitio, desde donde puedes reintentar la publicación cuando tu servidor vuelva a responder.
- ¿Cómo roto la API key del webhook?
- Desde «Modificar credenciales» puedes guardar una nueva clave; actualízala primero en tu servidor, o acepta ambas durante la transición, para no perder entregas.
- ¿Qué devuelvo si el artículo ya existía?
- Sobrescríbelo y responde 200 con la misma URL. El slug es la clave idempotente, así los reintentos nunca duplican contenido.
- ¿El servidor MCP sustituye a la API REST?
- No, son complementarios: el MCP está pensado para agentes de IA, la API REST para integraciones de backend tradicionales.
Empieza hoy sin tarjeta
Conecta tu web, recibe la auditoría y genera tu primera publicación optimizada gratis.
Probar gratis