Skip to content
ContentRemovalDeskDocumentation

Operate & monitor

Webhooks

Keep your product in sync with signed HTTPS events. Verify payloads, handle repeats, and recover missed events.

REST API · JSONv0.2.0 · Live CRD integration
On this page

Register your HTTPS receiver

After agency onboarding and KYC approval, the agency owner can add an endpoint under Webhooks in /agency/api. Management uses the authenticated agency session; it is not an API-key /v1 operation.

Use a publicly reachable HTTPS endpoint on port 443. Private, loopback, reserved network addresses, and redirects are rejected. Save the one-time signing secret. Register before starting work, because new endpoints receive future events.

Receiver URL formattext
https://your-domain.example/webhooks/crd

Understand the event envelope

Events include a stable id, type, workspace_id, created_at in epoch milliseconds, simulated: false, and a data object. Data fields depend on the event type. The X-CRD-Event-ID header contains the same event ID.

scan.completed eventjson
{
  "id": "evt_example",
  "type": "scan.completed",
  "workspace_id": "wsp_example",
  "created_at": 1790035200000,
  "simulated": false,
  "data": {
    "creator_id": "crt_example",
    "scan_id": "scn_example",
    "crd_job_id": "44444444-4444-4444-8444-444444444444"
  }
}

Verify the original request bytes

X-CRD-Signature has the form t=UNIX_SECONDS,v1=HEX_HMAC_SHA256. Compute HMAC-SHA256 over the timestamp, a dot, and the original body bytes using the endpoint signing secret.

Compare signatures in constant time and reject timestamps outside your tolerance. This example allows five minutes. Verify before JSON parsing; reserializing a parsed object can change the signed bytes.

Node.js · signature verificationjavascript
import { createHmac, timingSafeEqual } from 'node:crypto';

// Call with the original request bytes, before JSON parsing.
export function verifyCrdWebhook(rawBody, signatureHeader, secret) {
  if (!Buffer.isBuffer(rawBody) || !secret) return false;
  const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signatureHeader ?? '');
  if (!match) return false;

  const timestamp = Number(match[1]);
  const now = Math.floor(Date.now() / 1000);
  if (!Number.isSafeInteger(timestamp) || Math.abs(now - timestamp) > 300) {
    return false;
  }

  const expected = createHmac('sha256', secret)
    .update(match[1] + '.')
    .update(rawBody)
    .digest();
  const received = Buffer.from(match[2], 'hex');
  return received.length === expected.length
    && timingSafeEqual(received, expected);
}

// After verification: JSON.parse(rawBody.toString('utf8')).
// Deduplicate by event.id and persist work before returning 2xx.

Available events

EventWhen it happens
protection.activatedThe API scan schedule is enabled.
protection.pausedThe API scan schedule is paused.
scan.completedA real CRD scan finishes and its findings are available.
scan.failedA CRD scan reaches a failure state.
creator.updatedThe first or changed public creator state is observed, including identity, enrollment, authorization, and entitlement.
case.createdA finding from the creator’s native CRD leak records is first synchronized.
case.updatedA native finding changes, or a known API scan mapping is attached to the existing case.
case.deletedA previously synchronized native finding is no longer present after complete reconciliation.
deindex.status_changedThe underlying deindex processing state changes.

Read creator and case update payloads

A case.created event can describe an existing CRD finding being synchronized for the first time. Use the stable case_id to deduplicate it. After an update, refresh the creator or cases collection to obtain current state.

EventData
creator.updatedcreator_id and creator: the current public creator object, including authorization, entitlement, and eligible.
case.created / case.updatedcase_id, creator_id, and status. Native case events and case.updated include case with the synchronized public finding. Some API-scan case.created events contain only the IDs and status.
case.deletedcase_id and creator_id. Remove or archive that case in your integration’s view; a deletion event is not proof of content removal.

Know which changes produce events

Events cover current agency creators linked through the API, their synchronized native leak records, the API-owned scan schedule, API scans, and API deindex requests. creator.updated includes current public identity, enrollment, authorization, and entitlement state. It does not include legal documents, signatures, or private authorization evidence.

Synchronization happens on reads and through periodic worker reconciliation. Initial observation can emit creator.updated, and importing an existing finding emits case.created. These events describe observed state; multiple edits between checks may result in one update. Refresh the corresponding resource after an event. A creator transferred out of the agency is no longer available to the previous agency’s credentials or webhook receiver.

Accept, deduplicate, and reconcile

After verification, persist the event or its work before returning 2xx. Deduplicate by event.id. Deliveries may repeat or arrive out of order, so retrieve current resource state before applying a change.

Failed deliveries retry with exponential backoff, up to five attempts. Terminal deliveries can be replayed from the authenticated dashboard. Replays retain the original event ID.

Recover missed events

Use GET /v1/events?limit=25&after=LAST_EVENT_ID to recover your authorized event stream in insertion order. The feed and deliveries share event IDs.

Save the last event ID even when next_cursor is null, then use it for later polling. Cursors must belong to your authorized event stream. The maximum page size is 100.

Diagnose an update that has not arrived

There are no separate public billing, invoice-paid, KYC-approved, invitation-accepted, or mandate-approved event types. Creator eligibility changes are represented through creator.updated when observed. The event catalog above is the supported contract.

  • Confirm the endpoint is active in /agency/api and was registered before the event. New endpoints receive future events; they do not backfill older deliveries automatically.
  • Check Recent deliveries for pending, failed, or delivered status and attempt count. A delivered status means the receiver returned 2xx, not that your downstream application completed its processing.
  • Verify the receiver accepts public HTTPS on port 443 without a redirect and responds within the current five-second delivery timeout. Validate and enqueue the event before returning 2xx.
  • Use GET /v1/events to recover eligible recorded events. Replay a failed or delivered terminal delivery from the dashboard when you need another delivery of that same event ID.
  • Refresh current creator, cases, or deindex state. Periodic synchronization observes current state and may combine several edits. deindex.status_changed is emitted when its workflow status changes; do not rely on a separate event for every individual outcome-field change.
  • If agency verification, ownership, or creator membership changed, deliveries may be blocked by the current access checks. Replacing a key does not replace a webhook signing secret; after an ownership change, the new verified owner must create new credentials and endpoints.

Event delivery example

Delivery headers
Content-Type: application/json
X-CRD-Event-ID: evt_example
X-CRD-Signature: t=UNIX_SECONDS,v1=HEX_HMAC_SHA256
Example event · scan.completed
{
  "id": "evt_example",
  "type": "scan.completed",
  "workspace_id": "wsp_example",
  "created_at": 1790035200000,
  "simulated": false,
  "data": {
    "creator_id": "crt_example",
    "scan_id": "scn_example",
    "crd_job_id": "44444444-4444-4444-8444-444444444444"
  }
}

Verify the original request bytes with your endpoint’s signing secret. View the verification example.