Fictionite Integration
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:
- Fictionite sends content to a Cacuda webhook when content is created or updated
- Cacuda runs the content through the configured moderation pipeline
- Cacuda calls back to Fictionite with the result (approve, reject, or flag)
- Fictionite applies the moderation decision to the content
Setup
- Create a Cacuda account and generate an API key at /account
- Create separate automods for each content type you want to moderate
- Copy each automod's Webhook URL and Webhook Secret
- Build a callback endpoint in your Fictionite instance (see below)
- 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 });
});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:
| Automod | Content Type | Trigger Config |
|---|---|---|
| Cover Art Moderator | Images | imageField: "imageUrl" |
| Comment Moderator | Short text | contentField: "content" |
| Chapter Moderator | Long text | contentField: "content" |