Skip to content

Webhooks

Webhooks push events from Defentry to your backend over HTTPS. Register an endpoint once, then receive alarm notifications as they are created or Online Data (Delete My Data) lifecycle events as removal work progresses.

CRM-authenticated users can manage webhooks in the Defentry CRM under Webhooks (/webhooks), or your integration can provision them programmatically with a CRM token.

Feature flag

Webhook delivery is enabled per client via is_crm_webhooks_enabled. If your CRM user is redirected away from the Webhooks section, ask your account manager to enable the feature for your client.

Register a webhook

Via the API (CRM token)

http
POST /api/v1/webhook
Authorization: Bearer {crm_token}
Content-Type: application/json

{
  "title": "Production alarm feed",
  "url": "https://integrations.example.com/defentry/webhooks",
  "type": "new_alarm",
  "secret": "your-signing-secret",
  "is_active": true,
  "skip_ssl_verification": false,
  "enable_requests_history": true
}
FieldRequiredDescription
titleyesHuman-readable label (shown in the CRM).
urlyesHTTPS endpoint that accepts POST JSON. Must be reachable when created. Unique per type.
typeyesnew_alarm or delete_my_data_event — see Webhook types.
secretnoShared HMAC secret for signature verification. Omit to skip signing.
is_activeyesInactive webhooks are not called.
skip_ssl_verificationyesSet true only for local/dev endpoints with self-signed certificates.
enable_requests_historyyesWhen true, each delivery attempt is stored and viewable in the CRM / via the requests API.

List, update, and delete:

http
GET    /api/v1/webhooks
GET    /api/v1/webhook/{id}
PUT    /api/v1/webhook/{id}
DELETE /api/v1/webhook/{id}

Inspect delivery attempts (when history is enabled):

http
GET /api/v1/webhook/{id}/requests
GET /api/v1/webhook/{id}/requests/{requestId}

The API never returns the raw secret after creation — only has_secret: true.

Via the CRM

  1. Log in to the Defentry CRM with a user that has webhook access.
  2. Open Webhooks in the navigation.
  3. Click Create, choose the event type, paste your HTTPS URL, and optionally generate a signing secret.
  4. Enable Request history while testing so you can inspect payloads and response codes from the CRM UI.

You can maintain separate webhooks per environment (staging vs production) — each URL is registered independently with its own secret.

Webhook types

typeX-Webhook-Type headerWhen it fires
new_alarmnew_alarmA new identity alarm is created for any account on your client.
delete_my_data_eventdelete_my_data_eventAn Online Data event is recorded for an account (scan result, removal progress, manual instructions, etc.).

Register one webhook per type if you want separate endpoints. Each active webhook of a given type receives every event of that type.

Delivery format

Defentry POSTs JSON to your url.

Headers (every delivery)

HeaderDescription
Content-Typeapplication/json
X-Webhook-TypeSame as the webhook type (new_alarm or delete_my_data_event).
X-Webhook-IdUnique delivery UUID — use for idempotency.
X-Webhook-TimestampUnix timestamp (seconds) when the request was built.
X-Webhook-SignaturePresent only when a signing secret is configured. Format: v1=<hex>.

Body shape

new_alarm — full alarm record:

json
{
  "alarm": {
    "id": 12345,
    "text": "Email user@example.com was stolen from ExampleSite",
    "account_id": 67890,
    "severity": "High",
    "status": "NEW",
    "source": "LeakDb",
    "event_type": "email_hacked",
    "category_id": 3,
    "original_event_id": "evt-abc-123",
    "payload": "{\"website\":\"ExampleSite\"}",
    "date": "2026-07-08 10:15:00",
    "checked_at": "2026-07-08 10:15:00",
    "created_at": "2026-07-08 10:15:00",
    "updated_at": "2026-07-08 10:15:00"
  }
}

delete_my_data_event — Online Data event envelope:

json
{
  "event": {
    "type": "DATA_REAPPEARED",
    "severity": "WARNING",
    "account_id": 67890,
    "client_id": "your-client-id",
    "site_id": 42,
    "site_name": "example-broker.se",
    "site_base_url": "https://www.example-broker.se",
    "occurred_at": "2026-07-08T10:15:00+00:00"
  }
}

site_id, site_name, and site_base_url are null when the event is not tied to a specific broker site (for example, account-level events).

Return any 2xx response to acknowledge receipt. Non-retryable 4xx responses stop retries; transient failures are retried — see Idempotency and retries.

Verifying signatures

When a signing secret is configured, verify every request before processing:

  1. Read the raw request body (do not re-serialize JSON).

  2. Collect X-Webhook-Id, X-Webhook-Timestamp, and X-Webhook-Signature.

  3. Build the signing string:

    text
    signing_string = "v1:" + X-Webhook-Timestamp + ":" + X-Webhook-Id + ":" + raw_body
  4. Compute expected = "v1=" + hex(hmac_sha256(secret, signing_string)).

  5. Compare with X-Webhook-Signature using a constant-time comparison.

Optionally reject requests where X-Webhook-Timestamp is older than a few minutes to limit replay windows.

php
$rawBody = $request->getContent();
$webhookId = $request->header('X-Webhook-Id');
$timestamp = $request->header('X-Webhook-Timestamp');
$signature = $request->header('X-Webhook-Signature');

$signingString = "v1:{$timestamp}:{$webhookId}:{$rawBody}";
$expected = 'v1=' . hash_hmac('sha256', $signingString, $yourSecret, false);

if (!hash_equals($expected, $signature)) {
    abort(401, 'Invalid signature');
}

Idempotency and retries

  • Each delivery has a stable X-Webhook-Id across retry attempts.
  • Store processed IDs (cache or database with TTL). If you receive a duplicate ID, return 200 and skip handling.
  • Retryable status codes: 408, 409, 429, 500, 502, 503, 504, and network/timeouts. Non-retryable: 400, 401, 403, 404, 422.
  • Default schedule: up to 10 attempts with exponential backoff (10 s → 4 h).

new_alarm — alarm catalog

A new_alarm webhook fires whenever a new row is inserted into the alarms table for your client. Use event_type, source, severity, and category_id to route handling in your system.

Alarm categories (category_id)

IDCategoryTypical triggers
1identityNational ID / SSN monitoring (Bisnode, CreditSafe, etc.)
2financialFinancial identity signals
3darkwebEmail or phone found in a breach (LeakDB)
4credit-cardCredit card exposure (LeakDB)
5company-identityCompany registration / identity changes
6passportPassport watchlist hit (LeakDB)
7drivers-licenseDriver's license watchlist hit (LeakDB)

event_type values

LeakDB watchlist hits (source: LeakDb):

event_typeWatchlist item
email_hackedEmail address
phone_hackedPhone number
ssn_hackedNational ID / SSN
iban_hackedIBAN
credit_card_hackedCredit card
passport_hackedPassport
drivers_license_hackedDriver's license
domain_hackedDomain (legacy clients only)

Credit & identity bureau sources (source matches event_type):

source / event_typeMeaning
BisnodeBisnode identity monitoring alert
CreditSafeCreditSafe monitoring alert
CreditSafeConnectCreditSafe Connect alert
CreditSafeEnquiryCredit enquiry registered on the account
AsiakastietoFinnish credit bureau (Asiakastieto) alert

Severity and status

FieldValues
severityLow, Medium, High, Info
statusNEW (just created), CLOSED (resolved — webhooks fire on create only)

The human-readable summary is in text. Structured vendor data is in payload (JSON string). Correlate with your account using account_id.

To fetch or close alarms over the API, see Alarms.

delete_my_data_event — Online Data event catalog

These webhooks are emitted when defentry-deleteme-service appends a row to events_history and WEBHOOKS_DMD_EVENTS_ENABLED is active. The service publishes DmdEventOccurredEvent to RabbitMQ; api.gateway.v2 fans it out to every active delete_my_data_event webhook.

The payload shape is fixed — DmdEventOccurredEvent::toPublish() in defentry-deleteme-service:

FieldTypeDescription
typestringEventTypeEnum value (see tables below).
severitystringINFO, SUCCESS, or WARNING.
account_idintegerGateway account id (DmdAccount.account_id).
client_idstringPartner client id (DmdAccount.client_id).
site_idinteger | nullBroker site id when the event is site-scoped.
site_namestring | nullHuman-readable site name.
site_base_urlstring | nullSite base URL.
occurred_atstringISO-8601 timestamp (events_history.created_at).

Your endpoint receives { "event": { ... } } with X-Webhook-Type: delete_my_data_event.

The type values align with GET /api/v1/delete-my-data/events and removal status fields on GET /api/v1/delete-my-data/scan/status, but webhooks push them in real time — you do not need to poll.

Removal lifecycle (four webhook events)

These are the events added for CRM webhook delivery around removal progress:

event.typeSeverityWhen it fires (code path)Scan / removal status
DELETION_REQUEST_SENTSUCCESSPoASentToSiteListener after a removal request is sent to the broker.POA_SENT_TO_SITE
USER_MANUAL_INSTRUCTIONS_SENTINFOUserManualInstructionsSentListener after manual removal instructions are sent (SendManualInstructionsJob, email classifier, or ops command).USER_MANUAL_INSTRUCTIONS_SENT
DATA_DELETEDSUCCESSDataRemovedVerifiedListener when the broker confirms data was removed.DATA_REMOVED_VERIFIED
DATA_REAPPEAREDWARNINGDataReappearedListener when a re-scan finds previously removed data on the site again.DATA_REAPPEARED_DETECTED

Example — manual instructions required:

json
{
  "event": {
    "type": "USER_MANUAL_INSTRUCTIONS_SENT",
    "severity": "INFO",
    "account_id": 67890,
    "client_id": "testing",
    "site_id": 7,
    "site_name": "hitta.se",
    "site_base_url": "https://www.hitta.se",
    "occurred_at": "2026-07-08T12:15:00+00:00"
  }
}

Example — data confirmed removed:

json
{
  "event": {
    "type": "DATA_DELETED",
    "severity": "SUCCESS",
    "account_id": 67890,
    "client_id": "testing",
    "site_id": 7,
    "site_name": "hitta.se",
    "site_base_url": "https://www.hitta.se",
    "occurred_at": "2026-07-08T14:30:00+00:00"
  }
}

Scan events

Emitted by ParserService when a per-site scan completes (SCAN_COMPLETED):

event.typeSeverityWhen it fires
PERSONAL_DATA_FOUNDWARNINGScan matched the user's profile on the site.
PERSONAL_DATA_NOT_FOUNDSUCCESSScan completed with no personal-data match.

Account-level events

event.typeSeverityWhen it firesSite fields
POWER_OF_ATTORNEY_SIGNEDSUCCESSPoASignedListener after the end user completes signing.all null

site_id, site_name, and site_base_url are null only for account-level events such as POWER_OF_ATTORNEY_SIGNED.

For the full Online Data integration flow, see Online Data.

Testing

  1. Create a webhook with enable_requests_history: true.
  2. On a test account, run the Online Data flow (scan → sign → removal) or wait for a real alarm on an account with monitoring enabled.
  3. Inspect deliveries via GET /api/v1/webhook/{id}/requests or the CRM Webhooks → Requests view.
  4. Confirm your endpoint returns 2xx and that signature verification passes.

Internal-only endpoints

An alarm simulation endpoint exists for CRM testing but is not part of the published integration API or this documentation site.

For API access and technical questions, contact Defentry through your account manager.