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

# Webhooks

> Use ChainPatrol webhooks to receive real-time asset status and blocklist updates, verify signatures, and keep your wallet, app, or SecOps pipeline in sync.

## What is a webhook?

ChainPatrol uses webhooks to notify you about changes to our global blocklist.

This allows you to update your blocklist in real-time, without having to wait for a scheduled cron job to run.
This is especially useful if you opt to use our [Asset List](https://chainpatrol.com/docs/external-api/asset-list.md) endpoint to sync our data as you can update your blocklist as soon as we update ours.

## Managing Webhooks

You can manage your webhooks through the [ChainPatrol dashboard](https://app.chainpatrol.io/admin) under `Settings > Webhooks`. From here you can:

1. Create new webhooks
2. View and monitor webhook deliveries
3. Test webhooks with sample events
4. Enable/disable webhooks
5. Update webhook configurations
6. Delete webhooks

Each webhook delivery is logged and can be inspected for debugging purposes, including the full request and response details.

## Event Types

### `debug.test`

This event is used for testing your webhook endpoint. It is sent when you click the "Test" button for a webhook in the dashboard.

```json theme={null}
{
  "event": "debug.test",
  "created_at": "2023-08-31T00:00:00.000Z",
  "data": {}
}
```

### `asset.status-updated`

This event is sent when the status of an asset changes. The `status` field can be one of `BLOCKED`, `ALLOWED`, or `UNKNOWN`.

```json theme={null}
{
  "type": "asset.status-updated",
  "created_at": "2023-08-31T00:00:00.000Z",
  "data": {
    "asset_id": 132,
    "type": "URL",
    "content": "https://example.com",
    "status": "BLOCKED"
  }
}
```

### `asset.status-escalated`

This event is sent when ChainPatrol escalates a threat to your organization for review. Subscribe to it to find out when something needs a decision from your team, for example to open a ticket or ping an on-call channel.

It fires when:

* A ChainPatrol analyst passes a proposal to your organization for review.
* Your organization has Obligatory Admin Approval enabled and an analyst approves a proposal. The proposal then waits for your organization's final approval.

It is **not** sent when a proposal is escalated internally within the ChainPatrol team. It is also not sent for items moved into your review queue in bulk when Obligatory Admin Approval is first turned on.

```json theme={null}
{
  "type": "asset.status-escalated",
  "created_at": "2025-01-15T10:30:00.000Z",
  "data": {
    "asset_id": 12345,
    "content": "https://example.com/phishing",
    "type": "URL",
    "status": "UNKNOWN",
    "proposal_id": 67890,
    "organization_id": 100,
    "comment": "Staff confirmed this as: Phishing",
    "review_url": "https://app.chainpatrol.io/admin/your-org/review?proposalId=67890"
  }
}
```

| Field | Description |
| - | - |
| `asset_id` | ID of the escalated asset. |
| `content` | The asset itself, such as a URL, domain, or social media profile. |
| `type` | The asset type, such as `URL`. |
| `status` | The asset's current status (`BLOCKED`, `ALLOWED`, or `UNKNOWN`). Escalation doesn't change it, so it is often `UNKNOWN` while the proposal awaits your decision. |
| `proposal_id` | ID of the proposal awaiting your review. |
| `organization_id` | ID of your organization. |
| `comment` | The note left by the analyst who escalated the proposal. May be `null`. |
| `review_url` | Link to the proposal on your organization's review page in the ChainPatrol dashboard. |

> [!NOTE]
>
> If a proposal is escalated to your organization more than once, you receive an event each time. Use `proposal_id` to deduplicate.

### `asset.escalation-resolved`

This event is the counterpart of `asset.status-escalated`. It is sent once when a threat that was escalated to your organization no longer needs your review, so you can close whatever you opened when the escalation arrived.

It fires when:

* Someone in your organization approves, rejects, or watchlists the proposal, or passes it back to the ChainPatrol team.
* ChainPatrol resolves the escalation on your behalf, for example when an analyst pushes the proposal through while it is still waiting on you.

It is **not** sent for reviews that leave the proposal waiting on your organization.

```json theme={null}
{
  "type": "asset.escalation-resolved",
  "created_at": "2025-01-16T09:12:00.000Z",
  "data": {
    "asset_id": 12345,
    "content": "https://example.com/phishing",
    "type": "URL",
    "proposal_id": 67890,
    "organization_id": 100,
    "resolution": "APPROVED",
    "resolved_by": "CUSTOMER",
    "comment": "Confirmed, please block"
  }
}
```

| Field | Description |
| - | - |
| `asset_id` | ID of the asset. |
| `content` | The asset itself, such as a URL, domain, or social media profile. |
| `type` | The asset type, such as `URL`. |
| `proposal_id` | ID of the proposal that was escalated. Matches `proposal_id` from `asset.status-escalated`. |
| `organization_id` | ID of your organization. |
| `resolution` | How the escalation was resolved: `APPROVED`, `REJECTED`, `WATCHLISTED`, or `RETURNED_TO_CHAINPATROL` (passed back to the ChainPatrol team to decide). |
| `resolved_by` | `CUSTOMER` if someone in your organization resolved it, or `CHAINPATROL` if the ChainPatrol team did. |
| `comment` | The note left with the decision. May be `null`. |

> [!NOTE]
>
> The payload does not include the asset's status. Approving a proposal blocks the asset shortly afterwards rather than immediately, so subscribe to `asset.status-updated` if you need the final status.

### `organization.threat-detection-added`

This event is sent when a new suspected threat is added to the Suspected Threats page for your organization. A suspected threat is unconfirmed until it is reviewed. The event name and the `threat_detection_result_id` key keep the API's detection naming.

```json theme={null}
{
  "type": "organization.threat-detection-added",
  "created_at": "2025-08-20T14:35:39.062Z",
  "data": {
    "organization_id": 12,
    "threat_detection_result_id": 16525556
  }
}
```

### `audit-log.created`

This event is sent when an action is recorded in your organization's Audit Log, such as a brand being edited, a member being added or removed, or an API key being created. It carries what your admins see on the Audit Log page. Use it to stream those changes into a SIEM.

```json theme={null}
{
  "type": "audit-log.created",
  "created_at": "2026-11-03T15:04:05.000Z",
  "data": {
    "id": 98765,
    "event_type": "member.added",
    "occurred_at": "2026-11-03T15:04:04.000Z",
    "organization": { "id": 100, "slug": "your-org" },
    "actor": { "type": "user", "id": 42, "name": "Jane Admin", "email": "jane@example.com" },
    "target": { "type": "user", "id": 77, "name": "New Analyst", "email": "analyst@example.com" },
    "changes": {
      "before": null,
      "after": {
        "organizationId": 100,
        "userId": 77,
        "email": "analyst@example.com",
        "fullName": "New Analyst",
        "role": "MEMBER"
      }
    },
    "ip_address": "203.0.113.10",
    "user_agent": "Mozilla/5.0"
  }
}
```

| Field | Description |
| - | - |
| `id` | ID of the audit log entry. Use it to deduplicate. |
| `event_type` | What happened, such as `brand.updated`, `member.added`, `member.removed`, `service.setting.updated` or `api_key.created`. |
| `occurred_at` | When the action was recorded. |
| `organization` | Your organization's `id` and `slug`. |
| `actor` | Who did it. `type` is `user` for someone in your organization, `staff` for the ChainPatrol team, or `system` for an automatic change. Staff actions show `name` as `ChainPatrol Team` with an empty `email`. |
| `target` | What was changed, by `type` (such as `brand`, `organization`, `user` or `api_key`) and `id`. When the target is a user, `name` and `email` are included. |
| `changes` | The values `before` and `after` the action, where the action records them. Either may be `null`. |
| `ip_address` | IP address the action came from. `null` for staff actions and when none was recorded. |
| `user_agent` | Browser or client that made the request. `null` for staff actions and when none was recorded. |

## Webhook Configuration

When creating a webhook, you'll need to provide:

1. A HTTPS endpoint URL that will receive the webhook events
2. An optional description to help identify the webhook's purpose
3. Select which events you want to receive (available events include `asset.status-updated`, `asset.status-escalated`, `asset.escalation-resolved`, `organization.threat-detection-added`, and `audit-log.created`)
4. Optional HTTP headers for supported integrations (currently Google SecOps headers are available)

The webhook secret will be automatically generated and shown to you once when creating the webhook. Make sure to copy it as it won't be displayed again.

### Custom HTTP Headers

ChainPatrol supports specific hardcoded HTTP headers that can be configured for webhook requests. Currently, the following headers are available for Google SecOps integration:

* `X-goog-api-key`: Your Google API key
* `X-Webhook-Access-Key`: Your webhook access key

When configuring a webhook for Google SecOps, you can provide values for these headers, and they will be automatically included in all webhook requests sent to your endpoint, allowing Google SecOps to authenticate and process the webhook events.

## Signature Verification

ChainPatrol signs each webhook request with a signature to verify that the request originated from ChainPatrol. The signature is included in the `X-ChainPatrol-Signature` header of each request. The signature is computed by taking the SHA256 HMAC of the request body using your webhook secret as the key. The signature is encoded as a hex string.

```
sha256=hex(hmac_sha256(webhook_secret, request_body))
```

You can verify the signature of a request by computing the signature yourself and comparing it to the signature included in the request. If the two signatures match, then you can be sure that the request originated from ChainPatrol.

Here's an example of how to verify the signature in Node.js:

```js theme={null}
const crypto = require("node:crypto");

const [, signature] = req.headers["x-chainpatrol-signature"].split("=");

const expectedSignature = crypto
  .createHmac("sha256", process.env.WEBHOOK_SECRET)
  .update(req.body)
  .digest("hex");

if (signature !== expectedSignature) {
  throw new Error("Invalid signature");
}
```

## Delivery Status and Retries

Each webhook delivery attempt is logged with one of the following statuses:

* `PENDING`: The delivery is queued
* `IN_PROGRESS`: The delivery is being attempted
* `SUCCESS`: The delivery was successful (received 2xx response)
* `FAILURE`: The delivery failed

Webhooks will be retried up to 3 times with an exponential backoff if your endpoint returns a non-2xx status code. If your endpoint returns a 2xx status code, then the webhook will not be retried.

You can view the delivery status, payload, request, and response details for each webhook attempt in the dashboard.

## Security

Webhooks are sent over HTTPS and are signed with a secret that is unique to your account. You should:

1. Only use HTTPS endpoints
2. Keep your webhook secret secure and never commit it to version control
3. Always verify the signature of incoming webhooks
4. Contact us immediately if you suspect your webhook secret has been compromised

If you need to rotate your webhook secret, you can delete the existing webhook and create a new one with the same configuration.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.