Skip to main content

External Moderation Phases

External Moderation Phases are an advanced feature that allows you to integrate with Coral's moderation pipeline via HTTP. You will provide a callback URL that will receive POST requests from Coral when comments are posted. The provided URl must respond with a moderation decision within the designated timeout or the action will be skipped.

You can configure external moderation phases on your installation of Coral by visiting /admin/configure/moderation/phases.

Once you've configured a external moderation phase in Coral, you will start to receive moderation requests in the form of a External Moderation Requests at the provided callback URL. These will be in the form of POST requests with a JSON payload.

When a comment is created or edited, it will be processed by moderation phases in a predefined order. Any external moderation phase is run last, and only if all other moderation phases before it do not return a status. The current set of moderation phases is listed in order here.

Once you have received a moderation request, you must respond within the provided timeout else the phase will be skipped and it will continue. It is strongly recommended to verify the request signature.

The external moderation phase must respond with one of the following:

  1. Do not moderate the comment, and return a 204 without a body.
  2. Perform a moderation action and return a 200 with a External Moderation Response as a JSON encoded body containing the operations you want to perform on the comment.

Request Signing#

Requests sent by Coral for external moderation phases use the same process as those used by webhooks. Refer to the webhooks documentation for instructions on how to verify signatures sent by Coral.


External Moderation Request#

interface ExternalModerationRequest {  /**   * action refers to the specific operation being performed. If `NEW`, this   * is referring to a new comment being created. If `EDIT`, then this refers to   * an operation involving an edit operation on an existing Comment.   */  action: "NEW" | "EDIT";
  /**   * comment refers to the actual Comment data for the Comment being   * created/edited.   */  comment: {    /**     * body refers to the actual body text of the Comment being created/edited.     */    body: string;
    /**     * parentID is the identifier for the parent comment (if this Comment is a     * reply, null otherwise).     */    parentID: string | null;  };
  /**   * author refers to the User that is creating/editing the Comment.   */  author: {    /**     * id is the identifier for this User.     */    id: string;
    /**     * role refers to the role of this User.     */    role: "COMMENTER" | "STAFF" | "MODERATOR" | "ADMIN";  };
  /**   * story refers to the Story being commented on.   */  story: {    /**     * id is the identifier for this Story.     */    id: string;
    /**     * url is the URL for this Story.     */    url: string;  };
  /**   * site refers to the Site that the story being commented on belongs to.   */  site: {    /**     * id is the identifier for this Site.     */    id: string;  };
  /**   * tenantID is the identifer of the Tenant that this Comment is being   * created/edited on.   */  tenantID: string;
  /**   * tenantDomain is the domain that is associated with this Tenant that this   * Comment is being created/edited on.   */  tenantDomain: string;}


New comment on a story:

{  "action": "NEW",  "comment": {    "body": "Here's a comment!",    "parentID": null  },  "author": {    "id": "baf4e943-3594-4fcc-b2ba-3e8de7a76352",    "role": "COMMENTER"  },  "story": {    "id": "245b3856-b0a0-4d2f-a6bb-58c71f18d6a6",    "url": "http://localhost:1313/posts/a-story-url/"  },  "site": {    "id": "a4bede88-2d2c-4424-bc18-4322a9e285a6"  },  "tenantID": "19ba5794-7eeb-4d46-a81b-c00c61672501",  "tenantDomain": "localhost"}

New reply on a comment on a story:

{  "action": "NEW",  "comment": {    "body": "Here's a reply!",    "parentID": "d79b787f-f406-49a0-a179-72e3652e54be"  },  "author": {    "id": "baf4e943-3594-4fcc-b2ba-3e8de7a76352",    "role": "COMMENTER"  },  "story": {    "id": "245b3856-b0a0-4d2f-a6bb-58c71f18d6a6",    "url": "http://localhost:1313/posts/a-story-url/"  },  "site": {    "id": "a4bede88-2d2c-4424-bc18-4322a9e285a6"  },  "tenantID": "19ba5794-7eeb-4d46-a81b-c00c61672501",  "tenantDomain": "localhost"}

External Moderation Response#

interface ExternalModerationResponse {  /**   * actions is an optional list of any flags to be added to this Comment.   */  actions?: Array<{    actionType: "FLAG";    reason: "COMMENT_DETECTED_TOXIC" | "COMMENT_DETECTED_SPAM";  }>;
  /**   * tags are any listed tags that should be added to the comment.   */  tags?: Array<"FEATURED" | "STAFF">;
  /**   * status when provided decides and terminates the moderation process by   * setting the status of the comment.   */  status?: "NONE" | "APPROVED" | "REJECTED" | "PREMOD" | "SYSTEM_WITHHELD";}


Add a flag to a comment and do not set a status:

{  "actions": [{ "actionType": "FLAG", "reason": "COMMENT_DETECTED_TOXIC" }]}

Reject a comment:

{  "status": "REJECTED"}

Feature a comment and do not set a status:

{  "tags": ["FEATURED"]}

Approve a comment and mark it as featured:

{  "status": "APPROVED",  "tags": ["FEATURED"]}