Skip to content

Standard Integration

Note

Before you start, you must have access granted to Checkstep by our client solutions team.

Overview

graph LR
    P[Your platform] -- send content --> CS[Checkstep]
    CS -- webhooks --> P
    P -- redirect users --> T[Transparency]

Integrating with Checkstep is done via 3 steps:

  1. Send your contents via API to Checkstep
  2. Receive analysis and decision via Webhooks from Checkstep
  3. (optional) Redirect your end users to the transparency flow

Authentication

The Checkstep API uses API Keys to authenticate requests.

You can view and manage your API keys in Checkstep settings page for each platform you have provisioned.

Your API keys carry many privileges, please keep them secure. Do not share API keys publicly.

Authentication to the API is performed using Bearer authorization scheme by including an HTTP header, e.g., using a sample key: -H Authorization: Bearer ${CHECKSTEP_API_KEY}.

All API requests must be made over HTTPS. Calls made over plain HTTP will fail.

Sending contents to Checkstep

The first step in the integration journey is to ingest your content into Checkstep.

First, you have to decide how to structure your content in Checkstep.

Then three API options are available to you:

  • asynchronously
  • in bulk
  • synchronously

Structuring your content

In Checkstep, a piece of content is very flexible. It could contain many fields, combining text, image, audio or video to best suit your needs.

Each piece of content is uniquely identified by its type (also called complex type) and its unique id.

This identifier is required when sending a content in Checkstep, and is always returned to you in webhooks.

Few examples :

  • a profile content with id 1234, composed of a username, a profile picture and a biography
  • a post content with id a89zMzx, composed of a title, an image and a text
  • a comment content with id e6adc0dd-2f1c-4085-8d60-3a1e771b1d16, composed of a single text

Complex type

By default, Checkstep provides a standard list of complex types to pick from : profile, post, comment, thread, chat, product, review, reply, profile-fragment, post-fragment, product-fragment.

Tips

You can also have your own complex types (please contact the solution team to add them to your account).

As a piece of content could contain any combination of fields, it is important to pick your complex type according to the moderation lifecycle of your platform. If you plan to moderate one of your domain objects (enforce it, restore it), it should be sent as its own complex type.

Let's illustrate this concept with two possible setups for a post. Assuming a post is composed of a title (text), a body(text), a banner image (image) and many attachments (video,image,audio):

  • Option 1: you enforce the post as a whole. If one piece is breaching a policy (for example, the banner image), you are enforcing the entire post. You will want to send your content using the post type with all the different fields.
  • Option 2: you enforce part of the post. If one of the post's attachments is breaching a policy, you want to enforce it, but still keeping the post visible on your platform. You will want to send your content using the post type for most of the fields, and the attachments as post-fragment so you can moderate them on their own. You will still be able to express the relationship between the parent post and its attachments to have the full context when doing the moderation.

Relationships

When ingesting content into Checkstep, you have the possibility to link different pieces together with a parent-children relationship. Relationships are visible in the moderation UI to give context to your moderators. When reviewing a content:

  • the parent is displayed next to the content in review
  • few siblings are displayed next to the content in review

Author

It is important to designate the author of a piece of content, identifiable by a unique ID from your platform. This enables Checkstep to build an author profile, allowing tracking of any repeated offences. Instead of addressing individual pieces of content, this approach allows for direct action against the author.

Use this API method to send content for analysis. When it is fully analysed, you could get notified about the result via a webhook. You can generate a cURL snippet on Checkstep UI.

View OpenAPI Specification for the details about each field expected.

An example payload:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "author": "82a277f5-4714-4a09-bdb1-6b8726418ad8",
  "type": "profile",
  "fields": [
    {
      "id": "name",
      "type": "text",
      "src": "John Doe"
    },
    {
      "id": "bio",
      "type": "text",
      "src": "Hello I am John, an example person"
    }
  ]
}

The fields you are sending are flexible, as long as their identifiers are unique with their siblings. You must provide their type.

Warning

In the previous example there are multiple type properties. The first one "type": "profile" is the complex type of your content. These types are managed by you to describe your own domain. The following ones "type": "text" are field's simple type where possible values are defined by Checkstep according the ability to process and scan them.

Send partial content

It is possible to send your content partially in case a field gets updated. You can then just submit the field to update.

An example of partial payload:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "type": "profile",
  "fields": [
    {
      "id": "name",
      "type": "text",
      "src": "John Doe"
    },
    {
      "id": "bio",
      "type": "text",
      "src": "Hello, I am John!"
    }
  ]
}

Partial contents will be aggregated part of the same content case, as long as the complex type and content id are the same.

You don't need to repeat fields like author or source, the last known value will be used.

Parent relationship

To express a parent relationship, you have to submit the parent's complex type and id in your ingest query:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "author": "82a277f5-4714-4a09-bdb1-6b8726418ad8",
  "type": "comment",
  "fields": [
    {
      "id": "body",
      "type": "text",
      "src": "Aha true, I never thought about that"
    }
  ],
  "parent": {
    "type": "thread",
    "id": "26c1bce6-1f7e-46eb-b2bc-2e67604608d3"
  }
}

Nested fields

For some scenarios, you might be interested in grouping fields together. You can do that with the nested structure of a field.

An example of an image gallery, where each image has a caption you want also to scan:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "type": "gallery",
  "fields": [
    {
      "id": "image_1",
      "type": "image",
      "src": "https://picsum.photos/id/11/2500/1667",
      "fields": [
        {
          "id": "caption",
          "type": "text",
          "src": "Nice view on the lake!"
        }
      ]
    },
    {
      "id": "image_2",
      "type": "image",
      "src": "https://picsum.photos/id/29/4000/2670",
      "fields": [
        {
          "id": "caption",
          "type": "text",
          "src": "Hard to climb, but worth it"
        }
      ]
    }
  ]
}

Send your own evaluations

If you have classification or scoring on your platform, you can integrate them in Checkstep :

  • your evaluations will be collected in the content cases and visible while doing reviews or exploring the cases
  • the Checkstep analytic widgets will show information for your evaluations
  • you will be able to set policy rules with the labels from your evaluations

Note

You must have an external strategy configured on your account to be able to send your own evaluations, please contact Checkstep team if you need such a setup.

You can send them along your content, like in this example:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "author": "82a277f5-4714-4a09-bdb1-6b8726418ad8",
  "type": "profile",
  "fields": [
    {
      "id": "name",
      "type": "text",
      "src": "John Doe"
    },
    {
      "id": "bio",
      "type": "text",
      "src": "Hello I am John, an example person"
    }
  ],
  "evaluations": [
    {
      "label": "similar",
      "field": "name",
      "score": 0.9,
      "strategy": "external-multilabels"
    }
  ]
}

or asynchronously on their own :

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "type": "profile",
  "evaluations": [
    {
      "label": "similar",
      "field": "name",
      "score": 0.9,
      "strategy": "external-multilabels"
    }
  ]
}

Note

If you have nested fields, you can reference them in your evaluations by using a dot notation like image_1.caption for the nested example

Metadata

If you have some additional information about the content which you want to display to the moderators, you can send them as metadata.

Metadata can be attached to the whole content and sent with:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "author": "82a277f5-4714-4a09-bdb1-6b8726418ad8",
  "type": "profile",
  "fields": [
    {
      "id": "name",
      "type": "text",
      "src": "John Doe"
    }
  ],
  "metadata": [
    {
      "id": "Plan",
      "value": "Freemium"
    },
    {
      "id": "Theme",
      "value": "Dark"
    }
  ]
}

If some metadata has changed, you can send them partially, and they will be merged with previous ones:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "author": "82a277f5-4714-4a09-bdb1-6b8726418ad8",
  "type": "profile",
  "metadata": [
    {
      "id": "Plan",
      "value": "Premium"
    }
  ]
}

Note

Merging of metadata is overwriting values with the same id and just adding new elements to the metadata list. Resulting metadata for the example above will be [{"id": "Plan", "value": "Premium"}, {"id": "Theme", "value": "Dark"}].

If you have different metadata for different fields, you can attach them to the specific field on any level of nesting:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "type": "gallery",
  "fields": [
    {
      "id": "image_1",
      "type": "image",
      "src": "https://picsum.photos/id/11/2500/1667",
      "metadata": [
        {
          "id": "Uploaded at",
          "value": "2023-12-08T14:02:12.716Z"
        }
      ],
      "fields": [
        {
          "id": "caption",
          "type": "text",
          "src": "Nice view on the lake!",
          "metadata": [
            {
              "id": "Lang",
              "value": "EN"
            }
          ]
        }
      ]
    }
  ]
}

Tags

You can tag your content, so it can be classified in different queues or easily searchable in Checkstep.

You just have to send the list of tags under tags property:

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "author": "82a277f5-4714-4a09-bdb1-6b8726418ad8",
  "type": "video",
  "tags": [
    "#featured",
    "#popular"
  ]
}

Warning

All the tags must start with the # character. A query containing tags without # will be rejected.

Bulk ingestion

OpenAPI Specification

Use this API method to send contents in a similar fashion as asynchronous ingestion.

This method is useful when you want to bootstrap your account with existing contents, scan them to identify eventual policy violations, and avoid the round-trips of sending content one by one.

An example payload:

{
  "bulk": "2dd1fca2-e303-40de-9172-a7b710b64f99",
  "contents": [
    {
      "id": "520f3f31-769b-4b1d-aad1-92c7eb66801d",
      "author": "Hollis_Gislason",
      "type": "post",
      "fields": [
        {
          "id": "message",
          "type": "text",
          "src": "If we input the panel, we can get to the XSS capacitor through the online EXE transmitter!"
        }
      ]
    },
    {
      "id": "75c3f62f-812c-42d1-8e4e-c7e7b06eb363",
      "author": "Marianna_Rutherford",
      "type": "post",
      "fields": [
        {
          "id": "message",
          "type": "text",
          "src": "overriding the microchip won't do anything, we need to generate the 1080p COM program!"
        }
      ]
    },
    {
      "id": "de07ecf1-4d8e-4cc3-a294-140baa68e2ae",
      "author": "Brayan_Hermiston",
      "type": "post",
      "fields": [
        {
          "id": "message",
          "type": "text",
          "src": "Use the primary HTTP card, then you can reboot the solid state interface!"
        }
      ]
    }
  ]
}

Synchronous (limited)

A synchronous endpoint that processes user-generated content returning policy violations.

View the OpenAPI Specification for more details

Warning

The synchronous nature of this endpoint is limiting the amount of analysis Checkstep could run on your content.

An example payload:

{
  "id": "34a8f82bbb",
  "author": "4c0789faff",
  "type": "user-profile",
  "fields": [
    {
      "id": "name",
      "type": "text",
      "src": "John Doe"
    },
    {
      "id": "bio",
      "type": "text",
      "src": "Hello I am John, an example person"
    }
  ]
}

and an example JSON response without violations:

{
  "id": "34a8f82bbb",
  "author": "4c0789faff",
  "timestamp": "2021-06-27T10:20:18.019Z",
  "type": "user-profile",
  "violations": []
}

and with violations:

{
  "id": "34a8f82bbb",
  "author": "4c0789faff",
  "timestamp": "2021-06-27T10:20:18.019Z",
  "type": "user-profile",
  "violations": [
    {
      "policy": "VLC",
      "confidence": "check"
    },
    {
      "policy": "HTE",
      "confidence": "trust"
    }
  ]
}

Webhooks

Description

Webhooks are configurable within Checkstep.

Webhooks are sent when events happen within Checkstep, e.g., a content being analysed, taking action against a user or directly on a piece of user-generated content.

Webhooks will be sent to configurable URL which will be the webhook endpoint.

All webhooks will be sent as a JSON document, with the following mandatory fields

Field Type Description
timestamp Datetime ISO 8601 format with required timestamp YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]]] indicating the time of the action that triggered the webhook.
webhook_type String Uniquely identify the type of the event, analysed-content, decision, author-decision or incident-closed

The rest of the payload will vary depending on webhook_type described after.

Tip

The webhook type is also exposed as an HTTP header X-Webhook-Type so you don't have to parse the JSON payload in order to know the type. It is useful for routing or for picking an appropriate deserialization entity.

Warning

For backward compability, a webhook payload could contain deprecated fields that are not documented here. As rule of thumb for deserializing the payload, only try to map fields you are going to use and ignore any other extra fields.

analysed-content

This event is raised when a content gets analysed, shortly after it has been ingested into Checkstep.

Payload

Example of a content with a single potential violation

{
  "timestamp": "2022-10-18T20:37:35.464307+00:00",
  "webhook_type": "analysed-content",
  "content": {
    "id": "7e070",
    "type": "user-profile",
    "fields": [
      {
        "id": "name",
        "type": "text",
        "src": "John Doe"
      },
      {
        "id": "bio",
        "type": "text",
        "src": "Hello I am John, an example person"
      }
    ]
  },
  "violations": [
    {
      "policy": "PGM",
      "field": "profile_picture",
      "confidence": "check"
    }
  ]
}
Parameter Type Description
content JSON Data related to the piece of content the action was taken on.
violations List Indicates what triggered the webhook to send.

The below table lists the fields within content:

Parameter Type Description
id String Uniquely identify the piece of content.
type String Uniquely identify the content type.
fields List The list of fields that was analysed

The below table lists the fields within each item of fields:

Parameter Type Description
id String Uniquely identify the field.
type String Uniquely identify the field type.
src String The field value.
fields List Nested fields.

The below table lists the fields within each item of violations:

Parameter Type Description
policy String Uniquely identify the potentially violated policy.
field String The content field evaluated.
confidence String check or trust depending on configured thresholds

decision

This event is raised when a human moderator or the moderation bot takes a decision on content.

Payload

Example webhook of decision: act

{
  "timestamp": "2022-10-18T20:37:35.464307+00:00",
  "webhook_type": "decision",
  "decision": "act",
  "hint": "disable",
  "content": {
    "id": "be7da45e89",
    "type": "my-object",
    "fields": [
      "background_image"
    ]
  },
  "triggers": [
    {
      "type": "manual"
    }
  ],
  "moderator_id": "137ec1a9-64e6-4d2a-848d-6aa5c56e7c29"
}
Parameter Type Description
decision String The nature of the decision, one of act, dismiss, escalate, overturn, upheld.
hint String Uniquely identify the precise type of action.
content JSON Data related to the piece of content the decision was taken on.
triggers (optional) List Indicates what triggered the webhook to send.
moderator_id (optional) String A UUID uniquely identifying the moderator who took action if triggered manually.

The below table lists the fields within content:

Parameter Type Description
id String Uniquely identify the piece of content.
type String Uniquely identify the content type.

For processing a decision, you should:

  • check webhook_type == decision
  • check the decision
    • act you must take down the content
    • overturn you must restore a taken-down content
    • upheld the content must remain down, you can inform the user the appeal is rejected
  • use the content.type and content.id to identify the content you must act upon

author-decision

This event is raised when a human moderator takes a decision on author.

Payload

Example webhook of decision: act

{
  "timestamp": "2022-10-18T20:37:35.464307+00:00",
  "webhook_type": "decision",
  "decision": "act",
  "hint": "terminate",
  "author": "4c0789faff",
  "triggers": [
    {
      "type": "manual"
    }
  ],
  "moderator_id": "137ec1a9-64e6-4d2a-848d-6aa5c56e7c29"
}
Parameter Type Description
decision String The nature of the decision, one of act, escalate, overturn.
hint String Uniquely identify the precise type of action.
author JSON Identifier of author the decision was taken on.
triggers (optional) List Indicates what triggered the webhook to send.
moderator_id (optional) String A UUID uniquely identifying the moderator who took action if triggered manually.

For processing an author decision, you should:

  • check webhook_type == author-decision
  • check the decision
    • act you must suspend or terminate author's account
    • overturn you must restore author's account
  • use the author to identify the author's account you must act upon

incident-closed

This event is raised when an incident is actioned by the human moderator review or the moderation bot decision. It's a bit delayed after decision event, and it should be used to notify users about resolution.

Payload

Example of an incident closed with enforced resolution, because content was violating listed policies

{
  "timestamp": "2022-10-18T20:37:35.464307+00:00",
  "webhook_type": "incident-closed",
  "content": {
    "id": "be7da45e89",
    "type": "my-object"
  },
  "resolution": "enforced",
  "violations": [
    {
      "policy": "VLC"
    },
    {
      "policy": "HTE"
    }
  ],
  "reporters": [
    {
      "id": "jane.doe@domain.com"
    }
  ]
}
Parameter Type Description
content JSON Data related to the piece of content caused the incident.
resolution String Indicates how incident was resolved, one of enforced, dismissed, overturned, upheld, terminated.
violations List Policies marked as violated within incident resolution.
reporters List All reporters which community reports were included in evidence of the incident.

The below table lists the fields within content:

Parameter Type Description
id String Uniquely identify the piece of content.
type String Uniquely identify the content type.

The below table lists the fields within each item of violations:

Parameter Type Description
policy String Uniquely identify the violated policy.

The below table lists the fields within each item of reporters:

Parameter Type Description
id String Identifier of reporter from community report.

For processing an incident closed event, you should:

  • check webhook_type == incident-closed
  • check reporters for users which reported this content and can be informed about the resolution
  • check the resolution
    • enforced reports were accepted and the content was taken down
    • dismissed reports were not accepted and the content remains online
    • overturned previously taken-down content was restored due to the author's appeal was accepted
    • upheld previously taken-down content remains down due to the author's appel was rejected
    • terminated reports were accepted and author of the content was baned
  • use the content.type and content.id to identify the content you are informing about

Payload Signing (optional)

Verify the events that Checkstep sends to your webhook endpoints.

The following request headers will be present on webhooks

x-auth-signature: <X_AUTH_SIGNATURE>
x-auth-date: <X_AUTH_DATE>
x-auth-nonce: <X_AUTH_NONCE>

Verify x-auth-signature matches the computed signature

secret = '<SIGNING_KEY>'

# pluck signature, date, nonce from request headers
signature = headers['x-auth-signature']
date = headers['x-auth-date']
nonce = headers['x-auth-nonce']

# compose content from body, date, nonce and encode
content = f"{body}.{date}.{nonce}".encode("utf-8")

# compute hash of content
content_hash = sha256(content).hexdigest()

# compute hmac using secret and hash of content
computed_signature = hmac.new(bytes(secret, "utf-8"), bytes(content_hash, "utf-8"),
                              digestmod=hashlib.sha256).hexdigest()

# verify signature matches computed signature
assert signature == computed_signature
const secret = 'SIGNING_KEY';

// pluck signature, date, nonce from request headers
const signature = headers['x-auth-signature']
const date = headers['x-auth-date']
const nonce = headers['x-auth-nonce']

// compose content from body_content, date, nonce and encode
const content = `${body}.${date}.${nonce}`;

// compute hash of content
const contentHash = crypto.createHash('sha256').update(content).digest('hex');

// compute hmac using secret and hash of content
const computedSignature = crypto.createHmac('sha256', secret).update(contentHash).digest('hex');

// verify signature matches computed signature
(signature === computedSignature ? console.log('Success: Signature verified') :
    console.log('Error: Invalid signature'));

If a signing key is provided in the platform settings, the webhook payload will be signed using the signing key to verify the webhook has been sent from Checkstep.

Verifying signatures

A signature is provided in the request headers of each webhook that is sent, using the same signing key as the one provided, the signature can be verified to ensure the webhook was sent from Checkstep.

Computed Signature

The secret uses your platforms unique signing key which is then used alongside the hash value of the content to create a unique computed signature for hmac authentication. The signing key can be found in the platform settings.

Amazon SQS

All events can be sent also to Amazon SQS as messages with body containing the same payloads as described above.

Tip

Look for X-Webhook-Type in message attributes.

SQS is configurable by providing URL of the queue and ARN of the role with permissions to send messages to this queue. Provided role needs to allow to be assumed by Checkstep service account. Please contact the support team for getting the account identifier and more help with the setup.

An example of role permissions policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "sqs:SendMessage"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:sqs:YOUR_REGION:YOUR_ACCOUNT_ID:YOUR_QUEUE_NAME"
        }
    ]
}

An example of role trust policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com",
                "AWS": [
                    "arn:aws:iam::CHECKSTEP_ACCOUNT_ID:root"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

Transparency

Statement of reason and appeal

When you take a decision upon a content within Checkstep, manually or automatically, you will receive a webhook and hide this content on your platform. To be transparent with your end users, and according to several regulations, including the EU's DSA, you should give them explanations why their content was removed, and give them the right to appeal the decision.

If an author of an enforced content is connected and authenticated on your platform, you must present them their enforced contents, based on the received webhooks. For each enforced content, you must provide a "view more..." or "more info ..." button/link. On click, you can call the Checkstep redirect endpoint to get a unique link, then redirect your end user to the transparency portal.

OpenAPI Specification

sequenceDiagram
    actor A as Author
    participant P as Your Platform
    participant C as Checkstep
    participant T as Transparency
    A ->> P: authenticates
    P ->> A: returns enforced contents
    A ->> P: clicks on "View More..."
    P ->> C: calls redirect endpoint
    C ->> P: returns a unique location
    P ->> A: redirects to the unique location
    A ->> T: lands on the transparency portal

Warning

Do not share a location created with the redirect endpoint with someone else than the author of the content. The location returned by the API contains a unique authentication token that is not to be share or reuse.

Warning

The authentication token included in the unique location returned by the redirect endpoint will expired after 6 hours.

Custom domain name

By default, the portal is hosted on https://transparency.checkstep.com/.

Checkstep supports also custom domain names, please contact the support team to set up your own domain.