IllapaILLAPA
Documentation

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.

Request
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.

POST · application/json
{
  "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

FieldTypeDescription
illapa_execution_idstring (UUID)Unique execution ID. Useful for idempotency and avoiding duplicates.
titlestringGenerated article title.
slugstringSEO-friendly slug: lowercase, hyphens, no accents, max 60 characters.
descriptionstringShort SEO description (1-2 sentences, ~150 characters).
keywordsstring[]Array of relevant keywords for the article.
introductionstringIntroduction paragraph with the length configured in the template.
contentstringComplete article body in the chosen format (HTML, Markdown or text).
imagesstring[]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 CodeResult
2xx (200, 201, 204…)Execution marked as successfully delivered.
4xx or 5xxExecution marked as rejected. Your response body is saved for debugging.
Timeout / no responseExecution 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

app/api/webhook-illapa/route.ts
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)

server.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

WebhookController.php
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

main.py
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
ngrok gives you a URL like 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.

Example with PostgreSQL
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

Item in Rejected state: when your webhook returns an error code (4xx, 5xx) or doesn't respond, the item ends up in Rejected state in the Planner. The dashboard shows the exact body your server returned — no need to check external logs.

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.