Automatically delete users flagged by system?


Every day, I have a dozen spam accounts flagged as “This new user entered profile information without reading any topics or posts”.

In two years, we had 0 false positives.

So, I’m thinking that we could automatically delete these accounts if they are flagged for this specific reason.

Optionally, an automatic email could be sent to the recipient prior to the deletion, mentioning that the account was automatically deleted, but that the administrator can be reached out by email through the forum.

I took a look at Discourse Automation but it doesn’t allow that out of the box.

Would it be possible to code a plugin using discourse automation methods, or should it be coded from scratch?

Or is there an easier way to prevent this endless spam from going to our review list?


So, I’m giving a try to this, I’ll go with the webhooks that I’ve never used before.
I’ve started using Use Discourse webhooks with PHP as a template and it properly returns data to Discourse.

But I need to create reviewable spam items manually (like “user typed too fast” or “filled in their profile without reading any post”). Any idea how to do that?

I got it to work.

Creating a user with Insomnia, filling its profile with an Akismet trigger, which triggers a Review.
A few checks are done and the user is automatically deleted.

The Review properties for the script to trigger a user deletion are:

  • Type: ReviewableAkismetUser, ReviewableUser or ReviewableQueuedPost
  • Score > 0

I don’t want to automatically delete users flagged by other users than @system.

Here’s the PHP script I use:


// Immediately verify the authenticity of the request.
if (array_key_exists('HTTP_X_DISCOURSE_EVENT_SIGNATURE', $_SERVER)) {
    $discourse_payload_raw = file_get_contents('php://input');
    $discourse_payload_sha256 = substr($_SERVER['HTTP_X_DISCOURSE_EVENT_SIGNATURE'], 7);
    // For security, configure the webhook with a secret in Discourse and set it below.
    $discourse_payload_secret = '2J3tM5X4WYGkGp0tTkmu';
    // Verify that the request was sent from an authorized webhook.
    if (hash_hmac('sha256', $discourse_payload_raw, $discourse_payload_secret) == $discourse_payload_sha256) {
        echo 'received';
    else {
        die('authentication failed');
else {
    die('access denied');

// Prepare the payload for use in the PHP script.
$discourse_json = json_decode($discourse_payload_raw);

$reviewable = $discourse_json->reviewable;

// Set up the API URL
$api_url = "$reviewable->id/perform/delete_user?version=0";
//$api_url = "$reviewable->id/perform/delete_user?version=$reviewable->version";

// Verify that the "type" and "score" properties are valid
if (($reviewable->type == "ReviewableUser" || $reviewable->type == "ReviewableAkismetUser" || $reviewable->type == "ReviewableQueuedPost") && $reviewable->score > 0) {
  // Set up the curl options
    $options = array(
        CURLOPT_URL => $api_url,
        CURLOPT_CUSTOMREQUEST => "PUT", // Set the request method to PUT
        CURLOPT_HTTPHEADER => array(
            "Api-Key: 6666666666666666666666666666666666666666666",
            "Api-Username: system"
    // Initialize the curl session
    $curl = curl_init();
    curl_setopt_array($curl, $options);
    // Make the API call
    $response = curl_exec($curl);
    // Decode the response
    $response_data = json_decode($response);
}   else {


On a scale from 0 to 10… How ugly reliable it is, regarding the context in which it will work (0 false positives in 2 years for Discourse and akismet detections)?

I have a few questions about the review request payload initiated by Discourse when a user is detected as a potential spam account.

  1. Why are there 2 requests for 1 ReviewableUser with minimal differences between their respective payloads (the first request is on the left)?

  2. What does link_admin mean?

  3. When I delete manually a user (detected by Akismet) from the review panel, it triggers the review webhook with a new payload containing, among other things: "user_deleted": false. Shouldn’t this be true? :thinking:

  4. In the URL", does the “version” here refers to the “version” found in the payload? Should I care about this parameter? Should it be equal to the payload’s version parameter?

In the current state, I’m pretty confident that will work (well, mostly intuition…) and we can track potential issues by looking at the webhooks results…
Or list the reviewed items.

But there’s an issue here I can’t figure out.

Sometimes, the reviewed items list looks like this, without any information:

And sometimes it looks like this:

I’d like to keep all the info to be able to track more easily if there was a deleted false positive.

Any idea where this behavior comes from?

It seems to happen after an automatic deletion at some point, but I don’t know how and why.

I add that the JSON version of the reviewed items page contains all the missing information from Discourse’s interface.

Edit: I figured it out. This is what happens when you disable the Akismet plugin.

Just to note, I’ve seen this pop up a handful of times, and it was once a false positive: a sensible and caring new member filling out their profile first. So, it can happen.

1 Like

Yes. To be honest, I had 1 false positive (to my knowledge) in this span of more than two years.
The user then contacted me by email, which can easily be found on the website if a users encounter an issue.

In my case, and after reviewing manually thousands of users, the benefit of this auto-deletion would be in my opinion quite obvious, especially since it’s a niche forum – users that are really interested in the topic will contact us if they face any inconvenience. :slight_smile: