Documentation
Everything you need to integrate Illapa with your system. Configure the webhook once and receive generated content directly in your CMS, database or any HTTP endpoint.
Integration
Webhook Integration
When a template is in Automatic Webhook mode, Illapa sends a POST to your URL every time a post is generated. The content arrives structured and ready to insert into your database.
POST <your-webhook-url> Content-Type: application/json
Data
JSON Payload
The body sent to your webhook always includes the fixed fields plus the custom fields you configured in the template.
{
"illapa_execution_id": "a1b2c3d4-5678-abcd-...",
"title": "How to optimize your blog for SEO in 2025",
"slug": "optimize-blog-seo-2025",
"description": "Complete guide to rank your blog on Google...",
"keywords": ["SEO for blogs", "web positioning", "..."],
"introduction": "Introductory paragraph with hook and main keyword...",
"content": "<h2>Why SEO matters</h2><p>Full content...</p>",
"category_id": 5,
"author_slug": "john",
"status": "draft"
}Reference
Payload fields
Fixed fields
| Field | Type | Description |
|---|---|---|
| illapa_execution_id | string (UUID) | Unique execution ID. Useful for idempotency and avoiding duplicates. |
| title | string | Generated article title. |
| slug | string | SEO-friendly slug: lowercase, hyphens, no accents, max 60 characters. |
| description | string | Short SEO description (1-2 sentences, ~150 characters). |
| keywords | string[] | Array of relevant keywords for the article. |
| introduction | string | Introduction paragraph with the length configured in the template. |
| content | string | Complete article body in the chosen format (HTML, Markdown or text). |
| images | string[] | Image variant URLs (only if the template has images enabled). |
Custom fields
Fields you configure in the template are added to the root object alongside the fixed fields. Supported types: string, number, boolean.
{
"illapa_execution_id": "...",
"title": "...",
"content": "...",
"category_id": 5,
"author_slug": "john",
"status": "draft",
"featured": true
}Configuration
"content" field formats
Select the format in the template settings. The content field arrives in the chosen format.
HTML (default)
<h2>Subtitle</h2> <p>Article paragraph.</p> <ul> <li>List item</li> </ul>
Markdown
## Subtitle Article paragraph. - List item
Plain text
Subtitle Article paragraph. List item
Protocol
Expected response
Illapa evaluates your webhook response solely by HTTP status code. Your response body is stored for debugging on errors.
| HTTP Code | Result |
|---|---|
| 2xx (200, 201, 204…) | Execution marked as successfully delivered. |
| 4xx or 5xx | Execution marked as rejected. Your response body is saved for debugging. |
| Timeout / no response | Execution marked as rejected. |
Success response
HTTP/1.1 200 OK
{ "ok": true }Error response
HTTP/1.1 422 Unprocessable Entity
{ "error": "The category_id field is required" }Implementation
Code examples
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()
// Save the post to your database
// await db.insert(posts).values({ title, slug, content, description })
return NextResponse.json({ ok: true }, { status: 200 })
}Express (Node.js)
app.post('/api/webhook-illapa', async (req, res) => {
const {
illapa_execution_id,
title, description, keywords, introduction, content,
} = req.body
// Idempotency: check if you already processed this execution
// if (await alreadyProcessed(illapa_execution_id)) {
// return res.status(200).json({ ok: true, duplicate: true })
// }
// Save to your database, publish to your 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):
# Save to DB, etc.
return {"ok": True}Development
Local testing with ngrok
If your server runs on localhost, use ngrok to expose it publicly with a temporary URL.
ngrok http 3000
https://abc123.ngrok.io. Paste it in the Webhook URL field of the template in Illapa. The URL changes every time you restart ngrok (unless on a paid plan).Best practices
Idempotency
Each request includes illapa_execution_id — a unique UUID per execution. If your webhook can receive the same post more than once (manual retries), save the processed IDs to avoid duplicates.
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 })
}
// Process the post normally...
await db.query(
'INSERT INTO posts (execution_id, title, content) VALUES ($1, $2, $3)',
[illapa_execution_id, title, content]
)Troubleshooting
Debugging
Webhook receives nothing
- →Verify the URL is publicly accessible (not localhost).
- →If developing locally, use ngrok.
- →Check the template is in Webhook mode and verified.
Post arrives in wrong format
- →Check the Content Format in the template (HTML, Markdown, text).
- →Custom fields must match what your backend expects.
- →Check the JSON Payload tab on the generation screen.