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 content 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 content to Checkstep

The first step in the integration journey is to ingest your content into Checkstep. 3 options are available to you:

  • asynchronously
  • in bulk
  • synchronously

A piece of content will be uniquely identified by its complex type and its unique id. This information will always be required when sending a content in Checkstep, and always returned back to you in Webhooks

Few examples :

  • a user-profile content with id 1234
  • a post content with id a89zMzx
  • a album content with id e6adc0dd-2f1c-4085-8d60-3a1e771b1d16

Note

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

Note

Within your content, you can include metadata in the meta field. This metadata will be displayed in the moderation UI and can be utilized for searching contents, configuring queues, or for more advanced purposes.

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",
  "timestamp": "2023-11-15T09:32:44.716Z",
  "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 identifier 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.

Note

You can also send content using the deprecated body section instead of the flexible fields but it requires your account to be setup with the description of each field.

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",
  "timestamp": "2023-12-08T15:02:12.716Z",
  "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.

Nested fields

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

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

{
  "id": "009d8aa9-7d25-44ed-bbf5-8410528f96e1",
  "timestamp": "2023-12-08T15:02:12.716Z",
  "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 you 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 content 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",
  "timestamp": "2023-11-15T09:32:44.716Z",
  "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",
  "timestamp": "2023-11-15T09:32:44.716Z",
  "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

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",
      "timestamp": "2023-08-02T14:58:54.613Z",
      "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",
      "timestamp": "2023-08-02T14:58:54.614Z",
      "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",
      "timestamp": "2023-08-02T14:58:54.615Z",
      "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",
  "timestamp": "2021-06-27T10:20:18.019Z",
  "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 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 get 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
body JSON Content as it was analysed. Deprecated, only send if you ingest a content with body

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.

Payload

Example webhook of decision: act

{
  "timestamp": "2022-10-18T20:37:35.464307+00:00",
  "webhook_type": "decision",
  "decision": "act",
  "type": "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.
type 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.
fields (optional)(deprecated) List When a decision is specific to a field or a set of fields, expose them as a list. Absent if the decision is about the whole content.
Deprecated as decision must always be on the whole content

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

incident-closed

This event is raised when an incident is actioned by the human moderator review or the moderation bot decision. It's 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"
  },
  "resolutions": [
    "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.
resolutions List Indicates how incident was resolved, one or many 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 'platform settings'.

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, incl. 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.