> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whitecircle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time event notifications via HTTP webhooks

export const domain = 'https://eu.whitecircle.com';

White Circle can send HTTP POST requests to your server when specific events occur. This allows you to build custom integrations and automate workflows based on session reviews and strike-action updates.

## Available Events

| Event Type                  | Description                                                                                                                                                                  |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session.violated`          | A session has been flagged as violating one or more Policies.                                                                                                                |
| `review_manual.in_progress` | Someone from your team starts a manual review of the session and changes a model-generated verdict (for example, marking something as “false positive” or “false negative”). |
| `review_manual.completed`   | Reviewer finished the manual check and submitted the changes.                                                                                                                |
| `review_auto.completed`     | Our larger model has finished automatically checking the session.                                                                                                            |
| `strike.action.updated`     | A user’s effective strike action changed (for example `warn` → `suspend`).                                                                                                   |
| `strike.action.expired`     | A temporary strike action expired.                                                                                                                                           |

## Setting Up Webhooks

<Steps>
  <Step title="Open Workspace Settings">
    Go to your <Link href={`${domain}/settings/integrations`} target="_blank">Workspace Settings → Integrations</Link> page.
  </Step>

  <Step title="Add Webhook Endpoint">
    Click on the Webhooks integration and add your webhook URL.
  </Step>

  <Step title="Configure Events">
    Select which event types you want to receive notifications for.
  </Step>

  <Step title="Save">
    Click **Save** to activate the webhook.

    <Tip>
      You'll see the signing secret when creating a webhook. You'll need it later to [verify the authenticity of incoming events](/latest/integrations/webhooks#verifying-requests-from-white-circle).
    </Tip>
  </Step>
</Steps>

## Receiving events

Your webhook URL will receive a request for each event type you select. One request = one event.

<Tabs>
  <Tab title="sessions.violated">
    The `session.violated` event includes all flagged Policies for the session.
  </Tab>

  <Tab title="review_* events">
    For `review_manual.*` and `review_auto.completed` events, only the **changed** verdicts are included. For example, if five Policies are enabled in your environment and our larger model (or your colleague reviewing the session) changes the verdict on two of them, we will send you only the revised policies.
  </Tab>

  <Tab title="strike.action.* events">
    `strike.action.updated` and `strike.action.expired` include the user's current strike action state (`action`, `action_expires_at`) and strike list. Use these events to synchronize account restrictions in your app.
  </Tab>
</Tabs>

## Error handling

Please return an HTTP 200 OK for each event you successfully receive on your Webhook URL.

### Failure conditions

We consider any of these scenarios a single failure condition:

* We are unable to negotiate or validate your server's SSL certificate.
* We wait longer than 3 seconds to receive a valid response from your server.
* We receive any other response than an HTTP 200-series response.

### Retries

We'll retry a failed request up to 3 times in a gradually increasing timetable:

1. The first retry will be sent nearly immediately.
2. The second retry will be attempted after 2 seconds.
3. The third and final retry will be sent after 4 seconds.

<Tip>
  You can check the number of total requests and retries per last 24 hours on the platform.
</Tip>

<Frame>
  <img src="https://mintcdn.com/whitecircle/qmSGRNF95Ca4SZT_/images/webhook/2025-12-01/example-webhook.png?fit=max&auto=format&n=qmSGRNF95Ca4SZT_&q=85&s=ead806b799f73af486855e81224d06d4" alt="Webhook page" width="2804" height="810" data-path="images/webhook/2025-12-01/example-webhook.png" />
</Frame>

## Verifying requests from White Circle

With the help of signed secrets, you can verify whether requests from White Circle are authentic.

### Understanding signed secrets

You can verify requests from White Circle by verifying signatures using your signing secret.

On each HTTP request that White Circle sends, White Circle adds an `X-Whitecircle-Signature` HTTP header (or `x-whitecircle-signature` — header names are meant to be case-insensitive, so the letter case should not be assumed).

The signature is created by hashing the request body with the SHA-256 function, and combining it with an [HMAC](https://en.wikipedia.org/wiki/HMAC) signing secret. The resulting signature is unique to each request and doesn't contain any secret information, keeping your app secure.

Request signing follows this pattern:

* Your app receives a request from White Circle.
* Your app computes a signature based on the request.
* You make sure the signature you've computed matches the signature on the request.

Let's go over the recipe for this signature.

### Validating a request

<AccordionGroup>
  <Accordion title="Grab your White Circle Signing Secret and the request body" icon="1">
    ```python theme={null}
    >>> whitecircle_signing_secret = 'MY_WHITECIRCLE_SIGNING_SECRET'
    >>> request_body = request.text
    '{"event_type":"review_auto.completed","violation":true,"internal_id":"2e9f6747-045a-4713-9a60-8c166203fce7","violations":{"4fa864e4-9af1-43b6-9fbb-33627980041f":{"violation":true,"violation_source":["text"]},"35822a7e-dc4d-4b9a-a27b-c088e8c73f1c":{"violation":false,"violation_source":["text"]},"069167bb-bda5-4b2a-b012-e3ca78944951":{"violation":false,"violation_source":["text"]}}}'
    ```

    <Warning>
      Use the raw request body, without headers, before it has been deserialized from JSON or other forms.
      For example, in Python's Flask, use `request.get_data()` before accessing any other methods on the request in order to get the raw request payload, without performing JSON deserialization.
    </Warning>
  </Accordion>

  <Accordion title="Extract the timestamp header from the request" icon="2">
    The signature depends on the timestamp to protect against replay attacks. While you're extracting the timestamp, check to make sure that the request occurred recently. In this example, we verify that the timestamp does not differ from local time by more than five minutes.

    ```python theme={null}
    >>> import time
    >>> timestamp = request.headers['X-Whitecircle-Request-Timestamp']
    '1752847035'
    >>> time.time() - int(timestamp) < 5 * 60
    True
    ```
  </Accordion>

  <Accordion title="Concatenate the version number, the timestamp, and the request body together, using a colon (`:`) as a delimiter" icon="3">
    ```python theme={null}
    >>> sig_basestring = 'v0:' + timestamp + ':' + request_body
    'v0:1752847035:{"event_type":"review_auto.completed","violation":true,"internal_id":"2e9f6747-045a-4713-9a60-8c166203fce7","violations":{"4fa864e4-9af1-43b6-9fbb-33627980041f":{"violation":true,"violation_source":["text"]},"35822a7e-dc4d-4b9a-a27b-c088e8c73f1c":{"violation":false,"violation_source":["text"]},"069167bb-bda5-4b2a-b012-e3ca78944951":{"violation":false,"violation_source":["text"]}}}'
    ```
  </Accordion>

  <Accordion title="Hash the resulting string, using the signing secret as a key, and taking the hex digest of the hash" icon="4">
    ```python theme={null}
    >>> import hmac
    >>> import hashlib
    >>> my_signature = 'v0=' + hmac.new(whitecircle_signing_secret.encode(), sig_basestring.encode(), hashlib.sha256).hexdigest()
    'v0=37a8f9210398d5f370b2a10a4413ca16075d373e0c418d14c83ad71354912fbd'
    ```
  </Accordion>

  <Accordion title="Compare the resulting signature to the header on the request" icon="5">
    ```python theme={null}
    >>> whitecircle_signature = request.headers['X-Whitecircle-Signature']
    >>> hmac.compare_digest(my_signature, whitecircle_signature)
    True
    ```
  </Accordion>

  <Accordion title="Done!" icon="6">
    That's it! You may now proceed with processing the update.
  </Accordion>
</AccordionGroup>

## Request schema

<Tabs>
  <Tab title="Session/Review Event">
    <RequestExample>
      ```json Webhook request body theme={null}
      {
        "event_type": "review_auto.completed",
        "flagged": true,
        "internal_session_id": "80782a99-04b0-426b-8c59-f6a68ec08a26",
        "external_session_id": "9dad26de-d17b-47e9-8d6a-2a096cdff151",
        "policies": {
          "f714c7a0-0779-4838-894f-d05e9e6cd61f": {
            "flagged": false,
            "flagged_source": ["text"],
            "name": "Prohibited Content"
          },
          "5dd86eea-f13a-4a97-aa1e-ca5f76b63255": {
            "flagged": true,
            "flagged_source": ["text"],
            "name": "PII Detection"
          }
        }
      }
      ```
    </RequestExample>
  </Tab>

  <Tab title="Strike Action Event">
    <RequestExample>
      ```json Webhook request body theme={null}
      {
        "event_type": "strike.action.updated",
        "user_id": "user-123",
        "user_ids": ["user-123"],
        "action": "suspend",
        "action_expires_at": "2026-03-20T10:00:00Z",
        "strikes": [
           {
               "policy": {
                  "id": "8d3d8fad-0df7-443f-a6df-5445e48a8eaf",
                  "name": "adult",
                  "severity": "high"
              },
              "severity": "high",
              "points": 10,
              "created_at": 1770612473,
              "expires_at": 1771217273,
              "reason": "session.flagged",
              "internal_session_id": "5f8a31d6-4b2f-4e61-88d5-cc537e4af79e",
              "external_session_id": "session-456"
          }
        ]
      }
      ```
    </RequestExample>
  </Tab>
</Tabs>

<ResponseExample>
  ```python Webhook verification theme={null}
  import time, hmac, hashlib

  ## Step 1: Get secret and raw body
  whitecircle_signing_secret = 'MY_WHITECIRCLE_SIGNING_SECRET'
  request_body = request.text
  # '{"event_type":"review_auto.completed","flagged":true,"internal_session_id":"2e9f6747-045a-4713-9a60-8c166203fce7","policies":{"4fa864e4-9af1-43b6-9fbb-33627980041f":{"flagged":true,"flagged_source":["text"],"name":"Policy A"}}}'*/

  ## Step 2: Validate timestamp freshness
  timestamp = request.headers['X-Whitecircle-Request-Timestamp'] # '1752847035'
  time.time() - int(timestamp) < 5 * 60 # if True...

  ## Step 3: Build signature base string
  sig_basestring = 'v0:' + timestamp + ':' + request_body
  # 'v0:1752847035:{"event_type":"review_auto.completed","flagged":true,"internal_session_id":"2e9f6747-045a-4713-9a60-8c166203fce7","policies":{"4fa864e4-9af1-43b6-9fbb-33627980041f":{"flagged":true,"flagged_source":["text"],"name":"Policy A"}}}'

  ## Step 4: Compute HMAC-SHA256 signature
  my_signature = 'v0=' + hmac.new(whitecircle_signing_secret.encode(), sig_basestring.encode(), hashlib.sha256).hexdigest()
  # 'v0=37a8f9210398d5f370b2a10a4413ca16075d373e0c418d14c83ad71354912fbd'

  ## Step 5: Compare signatures
  whitecircle_signature = request.headers['X-Whitecircle-Signature']
  hmac.compare_digest(my_signature, whitecircle_signature) # if True...

  ## Step 6: Process the event
  request_body['event_type']...

  ## Step 7: Return 200 OK
  return '200 OK'
  ```
</ResponseExample>


## OpenAPI

````yaml latest/openapi-webhooks.json WEBHOOK /webhook
openapi: 3.1.0
info:
  title: WhiteCircle Webhooks
  description: Webhook events delivered to your endpoint
  version: '2025-12-01'
servers: []
security: []
paths: {}

````