Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.moderationapi.com/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks let you react to moderation events in real time. Whenever an event you subscribe to fires, we’ll POST a JSON payload to your URL. A single webhook can subscribe to any combination of events — you don’t need a separate URL per event type. Configure webhooks under Configure → Webhooks in the dashboard.

How it works

  1. Add a webhook in the dashboard, set its URL, and tick the events you care about.
  2. Whenever any of those events fire, we POST the event to your URL.
  3. Your endpoint returns a 2xx within 5 seconds to acknowledge.
  4. Anything else (timeout, 4xx, 5xx) triggers retries with exponential backoff — up to 5 attempts total.

Request headers

Every delivery includes:
HeaderDescription
webhook-versionPayload envelope version. Currently always v2.
webhook-event-idStable event ID (matches id in the body). Use this to dedupe retries.
modapi-signatureHMAC-SHA256 signature of the raw request body. See Verifying signatures.
User-AgentModAPI/1.0. Useful for allow-listing in security tools — see Troubleshooting.
Content-Typeapplication/json.

Payload envelope

Every event shares the same outer envelope. The event-specific data lives in data.object.
id
string
required
Stable event ID, prefixed with evt_. Identical across retries — use it to dedupe.
type
string
required
The event type, e.g. queue_item.rejected or author.blocked. See the event catalog.
api_version
string
required
Always v2.
created
string
required
ISO 8601 timestamp of when the event was emitted.
data
object
required
Wraps the event-specific payload.
Envelope shape
{
  "id": "evt_clxxx...",
  "type": "queue_item.rejected",
  "api_version": "v2",
  "created": "2026-05-08T12:34:56.789Z",
  "data": {
    "object": {
      /* event-specific resource */
    }
  }
}
Need typed payloads? The full OpenAPI schema for every event lives under components.schemas in our OpenAPI spec — look for WebhookEvent (a discriminated union over type) and the per-event schemas like QueueItemRejectedEvent, AuthorBlockedEvent, etc.

Event catalog

EventFires whendata.object
queue_item.resolvedA moderator marks a queue item as resolved (checked off the queue).The item, plus optional author and queue references.
queue_item.actionA custom moderation action runs on a queue item.The action that ran, with the related item, author, and queue.
queue_item.rejectedThe built-in reject action runs on a queue item.Same shape as queue_item.action.
queue_item.allowedThe built-in allow action runs on a queue item.Same shape as queue_item.action.
author.blockedAn author transitions to the blocked status.The action that drove the transition, with the affected author.
author.unblockedAn author transitions back to enabled. May fire automatically when a temporary suspension expires.Same shape as author.blocked.
author.suspendedAn author is suspended for a finite period. The nested author.block.until indicates when the suspension lifts.Same shape as author.blocked.
author.updatedPublic author fields (name, email, profile picture, metadata, etc.) change. Status transitions don’t trigger this.The updated author.
author.trust_level_changedAn author’s resolved trust level transitions to a new value. Doesn’t fire for no-op recomputes.The updated author.
author.actionA custom action runs against an author.Same shape as author.blocked.
Reject and allow are first-class events. If you want to capture every moderation action firing on a queue item, subscribe to queue_item.action and queue_item.rejected and queue_item.allowed. Built-in actions don’t fire under queue_item.action.

Routing by content type

Queue-item events carry the item’s meta_type — a high-level classifier you set when submitting content (or inherit from the channel). Use it to fan a single webhook out to the right handler per entity:
meta_typeTypical use
profileUser profiles
messageDMs / chat
postLong-form posts
commentReplies
eventEvent listings
productMarketplace
reviewRatings/reviews
otherAnything else
Webhook router
import type { WebhookEvent } from "@moderation-api/sdk";

const route = (event: WebhookEvent) => {
  switch (event.type) {
    case "queue_item.rejected":
      switch (event.data.object.item?.meta_type) {
        case "event":
          return rejectEvent(event);
        case "profile":
          return rejectProfile(event);
        case "review":
          return rejectReview(event);
      }
      break;

    case "queue_item.allowed":
      switch (event.data.object.item?.meta_type) {
        case "event":
          return allowEvent(event);
        case "profile":
          return allowProfile(event);
        case "review":
          return allowReview(event);
      }
      break;
  }
  return null;
};
Set meta_type per submission via the type field on the moderation endpoint, or configure it as the default for a channel.

Verifying signatures

Each delivery is signed with HMAC-SHA256 using your project’s webhook secret. Find the secret under API Keys in the dashboard. To verify, compute HMAC_SHA256(rawRequestBody, webhookSecret) as a hex digest and compare it to the modapi-signature header. Always use a constant-time comparison.
import ModerationAPI from "@moderation-api/sdk";

// Reads MODAPI_SECRET_KEY by default
const client = new ModerationAPI();

export async function POST(request) {
  const rawBody = await request.text();
  const signatureHeader = request.headers.get("modapi-signature") ?? "";

  // Verifies the signature with MODAPI_WEBHOOK_SECRET (or pass it explicitly
  // as a third arg). Throws if the signature is invalid.
  const event = client.webhooks.constructEvent(
    Buffer.from(rawBody),
    signatureHeader,
  );

  // `event` is typed as the WebhookEvent discriminated union — switching on
  // `event.type` narrows `event.data.object` to the right resource.
  switch (event.type) {
    case "queue_item.rejected":
      // event.data.object is the action_performed record
      break;
    case "author.blocked":
      // event.data.object.author is the blocked author
      break;
    // ...
  }

  return Response.json({ received: true });
}

Preventing replay attacks

The envelope created timestamp lets you reject events older than your tolerance window (e.g. 5 minutes). Combine that with the stable webhook-event-id header to dedupe retries — store recently-seen IDs and ignore repeats.

Retries and delivery

  • Success: any 2xx response within 5 seconds closes the delivery.
  • Failure: any non-2xx, timeout, or network error triggers a retry.
  • Limits: up to 5 attempts total, with exponential backoff between tries.
  • Permanent failure: after the final attempt fails, we’ll email the project’s admin.
You can inspect every delivery attempt — request headers, payload, response status, and response body — under Events log in the dashboard.

Troubleshooting

Check the events log

If webhooks aren’t behaving as expected, the events log is the first place to look. It shows every delivery attempt with full request and response details.

Allow Moderation API through your firewall

Some hosting providers and security services block webhook traffic by default. Cloudflare’s Bot Fight Mode, for example, will challenge the request instead of letting it through.

Common blocking scenarios

  • Bot Fight Mode / Super Bot Fight Mode is enabled
  • WAF Managed Rules active
  • Custom security rules

Choose your solution based on your plan

✅ Use Security Rules (Recommended)
  1. Go to your Cloudflare dashboard
  2. Select your domain
  3. Navigate to Security → Security rules
  4. Create a custom rule:
    • Name: “Moderation API Webhooks”
    • Field: User Agent
    • Operator: starts with
    • Value: ModAPI/
    • Action: Skip All Super Bot Fight Mode Rules and other rules that might interfere.
  5. Make sure to place this rule before other rules
  6. Deploy the rule

Still having issues?

  1. Check your hosting provider’s security logs for blocked ModAPI/ requests.
  2. Verify your endpoint returns a 2xx status code within 5 seconds.
  3. Test with a minimal endpoint (just respond 200 OK) to isolate the issue.
  4. Make sure firewall allow rules don’t conflict with deny rules earlier in the chain.
Need a hand? Email support@moderationapi.com and we’ll help you configure or allowlist for your account.