Fictionite Integration

8 min read

Fictionite integrates with Cacuda using the Other / Webhook trigger. Create separate automods for each content type — cover art, comments, and chapters — each with its own webhook URL and moderation pipeline.

Architecture

The integration uses a webhook-and-callback pattern:

  1. Fictionite sends content to a Cacuda webhook when content is created or updated
  2. Cacuda runs the content through the configured moderation pipeline
  3. Cacuda calls back to Fictionite with the result (approve, reject, or flag)
  4. Fictionite applies the moderation decision to the content

Setup

  1. Create a Cacuda account and generate an API key at /account
  2. Create separate automods for each content type you want to moderate
  3. Copy each automod's Webhook URL and Webhook Secret
  4. Build a callback endpoint in your Fictionite instance (see below)
  5. Configure the callback URL on each automod's approve/reject/flag action nodes

Content Types & Recommended Presets

Cover Art

Image moderation for book/story cover uploads. Set the webhook trigger's imageField to the field containing the image URL.

Recommended presets: Quick NSFW Image Check, Quick AI Image Check, or Image Scanner

Example payload

{
  "id": "cover_abc123",
  "type": "cover_art",
  "imageUrl": "https://cdn.fictionite.com/covers/abc123.jpg",
  "storyId": "story_456",
  "userId": "user_789"
}

Comments

Text moderation for reader comments. The default contentField of content works out of the box.

Recommended presets: Quick Spam Check, Quick NSFW Check, or Profanity Filter

Example payload

{
  "id": "comment_abc123",
  "type": "comment",
  "content": "Great chapter! I loved the twist at the end.",
  "chapterId": "chapter_456",
  "userId": "user_789"
}

Chapters / Posts

Long-form text moderation for published chapters. Use the default contentField of content.

Recommended presets: Quick AI Text Check, Full Spectrum Scanner, or Profanity Filter

Example payload

{
  "id": "chapter_abc123",
  "type": "chapter",
  "content": "Chapter 12: The darkness gathered at the edge of the forest...",
  "storyId": "story_456",
  "userId": "user_789"
}

Sending Content to Cacuda

POST your content to the automod's webhook URL with an HMAC-SHA256 signature in the X-Webhook-Signature header:

Example (Node.js)

import crypto from 'crypto';

const payload = JSON.stringify({
  id: comment.id,
  type: 'comment',
  content: comment.body,
  chapterId: comment.chapterId,
  userId: comment.authorId,
});

const signature = crypto
  .createHmac('sha256', CACUDA_WEBHOOK_SECRET)
  .update(payload)
  .digest('hex');

await fetch(CACUDA_WEBHOOK_URL, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Webhook-Signature': signature,
  },
  body: payload,
});

Building the Callback Endpoint

When the automod pipeline finishes, Cacuda POSTs the result to the callback URL configured on the action nodes. Build a POST /api/cacuda/moderate endpoint in Fictionite to handle these callbacks.

Callback payload shape

{
  "action": "approve" | "reject" | "flag",
  "contentId": "comment_abc123",
  "reason": "Spam detected",          // reject only
  "priority": "high",                  // flag only
  "note": "Needs manual review",       // flag only
  "results": {
    "spam": { "score": 0.92, "label": "spam" },
    "nsfw-txt": { "score": 0.05, "label": "clean" }
  }
}

Example handler (Node.js / Express)

app.post('/api/cacuda/moderate', async (req, res) => {
  const { action, contentId, reason, priority } = req.body;

  switch (action) {
    case 'approve':
      await db.content.update({
        where: { id: contentId },
        data: { status: 'published' },
      });
      break;
    case 'reject':
      await db.content.update({
        where: { id: contentId },
        data: { status: 'rejected', rejectReason: reason },
      });
      break;
    case 'flag':
      await db.moderationQueue.create({
        data: { contentId, priority, status: 'pending_review' },
      });
      break;
  }

  res.json({ ok: true });
});
Tip: Keep content in a pending state until the callback arrives. This prevents unmoderated content from being visible to readers.

One Automod per Content Type

Create separate automods for each content type. Each gets its own webhook URL, allowing you to tailor the moderation pipeline to the content:

AutomodContent TypeTrigger Config
Cover Art ModeratorImagesimageField: "imageUrl"
Comment ModeratorShort textcontentField: "content"
Chapter ModeratorLong textcontentField: "content"