Appearance
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
}| Field | Required | Description |
|---|---|---|
title | yes | Human-readable label (shown in the CRM). |
url | yes | HTTPS endpoint that accepts POST JSON. Must be reachable when created. Unique per type. |
type | yes | new_alarm or delete_my_data_event — see Webhook types. |
secret | no | Shared HMAC secret for signature verification. Omit to skip signing. |
is_active | yes | Inactive webhooks are not called. |
skip_ssl_verification | yes | Set true only for local/dev endpoints with self-signed certificates. |
enable_requests_history | yes | When 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
- Log in to the Defentry CRM with a user that has webhook access.
- Open Webhooks in the navigation.
- Click Create, choose the event type, paste your HTTPS URL, and optionally generate a signing secret.
- 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
type | X-Webhook-Type header | When it fires |
|---|---|---|
new_alarm | new_alarm | A new identity alarm is created for any account on your client. |
delete_my_data_event | delete_my_data_event | An 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)
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-Type | Same as the webhook type (new_alarm or delete_my_data_event). |
X-Webhook-Id | Unique delivery UUID — use for idempotency. |
X-Webhook-Timestamp | Unix timestamp (seconds) when the request was built. |
X-Webhook-Signature | Present 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:
Read the raw request body (do not re-serialize JSON).
Collect
X-Webhook-Id,X-Webhook-Timestamp, andX-Webhook-Signature.Build the signing string:
textsigning_string = "v1:" + X-Webhook-Timestamp + ":" + X-Webhook-Id + ":" + raw_bodyCompute
expected = "v1=" + hex(hmac_sha256(secret, signing_string)).Compare with
X-Webhook-Signatureusing 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-Idacross retry attempts. - Store processed IDs (cache or database with TTL). If you receive a duplicate ID, return
200and 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)
| ID | Category | Typical triggers |
|---|---|---|
| 1 | identity | National ID / SSN monitoring (Bisnode, CreditSafe, etc.) |
| 2 | financial | Financial identity signals |
| 3 | darkweb | Email or phone found in a breach (LeakDB) |
| 4 | credit-card | Credit card exposure (LeakDB) |
| 5 | company-identity | Company registration / identity changes |
| 6 | passport | Passport watchlist hit (LeakDB) |
| 7 | drivers-license | Driver's license watchlist hit (LeakDB) |
event_type values
LeakDB watchlist hits (source: LeakDb):
event_type | Watchlist item |
|---|---|
email_hacked | Email address |
phone_hacked | Phone number |
ssn_hacked | National ID / SSN |
iban_hacked | IBAN |
credit_card_hacked | Credit card |
passport_hacked | Passport |
drivers_license_hacked | Driver's license |
domain_hacked | Domain (legacy clients only) |
Credit & identity bureau sources (source matches event_type):
source / event_type | Meaning |
|---|---|
Bisnode | Bisnode identity monitoring alert |
CreditSafe | CreditSafe monitoring alert |
CreditSafeConnect | CreditSafe Connect alert |
CreditSafeEnquiry | Credit enquiry registered on the account |
Asiakastieto | Finnish credit bureau (Asiakastieto) alert |
Severity and status
| Field | Values |
|---|---|
severity | Low, Medium, High, Info |
status | NEW (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:
| Field | Type | Description |
|---|---|---|
type | string | EventTypeEnum value (see tables below). |
severity | string | INFO, SUCCESS, or WARNING. |
account_id | integer | Gateway account id (DmdAccount.account_id). |
client_id | string | Partner client id (DmdAccount.client_id). |
site_id | integer | null | Broker site id when the event is site-scoped. |
site_name | string | null | Human-readable site name. |
site_base_url | string | null | Site base URL. |
occurred_at | string | ISO-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.type | Severity | When it fires (code path) | Scan / removal status |
|---|---|---|---|
DELETION_REQUEST_SENT | SUCCESS | PoASentToSiteListener after a removal request is sent to the broker. | POA_SENT_TO_SITE |
USER_MANUAL_INSTRUCTIONS_SENT | INFO | UserManualInstructionsSentListener after manual removal instructions are sent (SendManualInstructionsJob, email classifier, or ops command). | USER_MANUAL_INSTRUCTIONS_SENT |
DATA_DELETED | SUCCESS | DataRemovedVerifiedListener when the broker confirms data was removed. | DATA_REMOVED_VERIFIED |
DATA_REAPPEARED | WARNING | DataReappearedListener 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.type | Severity | When it fires |
|---|---|---|
PERSONAL_DATA_FOUND | WARNING | Scan matched the user's profile on the site. |
PERSONAL_DATA_NOT_FOUND | SUCCESS | Scan completed with no personal-data match. |
Account-level events
event.type | Severity | When it fires | Site fields |
|---|---|---|---|
POWER_OF_ATTORNEY_SIGNED | SUCCESS | PoASignedListener 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
- Create a webhook with
enable_requests_history: true. - On a test account, run the Online Data flow (scan → sign → removal) or wait for a real alarm on an account with monitoring enabled.
- Inspect deliveries via
GET /api/v1/webhook/{id}/requestsor the CRM Webhooks → Requests view. - Confirm your endpoint returns
2xxand 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.
Related
- Alarms — polling and closing alarms via the API
- Online Data — scan, sign, and removal flows
- Onboarding — CRM token setup