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.8.0 · Live CRD integration
On this page

Configure your receiver in the portal

Open API → Webhooks, then Add endpoint. Enter your public HTTPS receiver and select its events.

Agency webhook endpoint controls
Demo workspace before an endpoint is configured.

Register your HTTPS receiver

Open /agency/api as the agency owner, select Webhooks, then register your receiver URL. Save the signing secret when it is shown. Registration uses your authenticated agency session; there is no public POST /v1/webhooks endpoint.

Use a publicly reachable HTTPS endpoint on port 443. Private, loopback and reserved addresses and redirects are rejected. A localhost URL cannot receive live deliveries; use your own public HTTPS test receiver. Registration applies to future events, so register before triggering work.

An API key authenticates your backend’s outgoing CRD requests. The endpoint signing secret verifies incoming webhooks. Store both server-side and do not interchange them. To replace a lost signing secret, register a replacement endpoint, update your receiver, then disable the old endpoint. Handle duplicate events during overlap.

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
impersonation.createdA native impersonation record is first observed, including historical imports.
impersonation.updatedAn impersonation record changes status or public details.
impersonation.deletedAn impersonation record is absent after a complete successful reconciliation.
protection.activatedActive API protection is enabled: the usage interval starts and eligible manual scan requests are accepted.
protection.pausedActive API protection is paused: the usage interval ends and new manual scan requests are refused.
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.
escalation.status_changedA paid legal-escalation request changes payment state or observed native workflow. Poll the resource for intermediate dispatching or uncertain states.

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, API protection changes, API scan outcomes, and API-created legal-escalation requests. creator.updated includes public identity, enrollment, authorization and entitlement, without legal documents or signatures.

The case event stream covers leak records. Impersonation updates use their own impersonation.created/updated/deleted events. Customer-safe notice history is available through GET /v1/creators/{id}/activity; it is not a webhook for every outgoing notice or a Google-removal guarantee.

Synchronization runs on reads and periodic worker reconciliation. An initial import can emit creator.updated or case.created. Several edits between checks may produce a single observed update. A creator transferred out of the agency is no longer available to its previous credentials or webhook receiver.

Accept, deduplicate, and reconcile

Verify the signature, parse the event, then atomically save its ID and work in durable storage before returning any 2xx response. On a duplicate ID, acknowledge the delivery without repeating its side effects. If storage fails, return a non-2xx response so CRD can retry.

Deliveries may repeat or arrive out of order. The receiver has five seconds to respond; process slow work asynchronously. Refresh current resource state instead of applying an old payload as the latest version.

There are up to eight total delivery attempts. After failures, retries become eligible after 10 seconds, 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 16 hours 40 minutes, spanning about 25 hours; worker availability can delay them. Any 2xx succeeds. Non-2xx responses, redirects, timeouts and transport errors count as failures. Terminal failed or delivered records can be replayed in the agency dashboard. Replays retain the original event ID, reset the attempt count and receive a fresh signature timestamp.

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 or cases state. Periodic synchronization observes current state and may combine several edits.
  • 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.