API Integration

Act on Kade signals in real time.

Outbound webhooks send location, client, cluster, email, and country-risk events to your backend or a connected Slack channel. This guide describes the payloads emitted by the current implementation and the API used to manage subscriptions.

5 event types URL and Slack destinations 5 delivery attempts 200 pings per batch maximum

Delivery contract

URL destinations receive an HTTP POST with a JSON body. Any 2xx response is successful. A non-2xx response or network failure is retried, for 5 total attempts, with exponential backoff beginning at 5 seconds.

At-least-once delivery

Retries can deliver the same event more than once. Make processing idempotent using X-Webhook-ID, which identifies the delivery event and remains the same across its retry attempts.

Acknowledge quickly

Validate, enqueue durable work, and return a 2xx response quickly. Run expensive processing outside the request path.

Request headers

HeaderWhen presentMeaning
Content-TypeAlwaysapplication/json
X-Webhook-EventAlwaysOne of the five event names documented below.
X-Webhook-IDAlwaysUnique delivery event ID. Use as the idempotency key.
X-Webhook-BatchBatched deliveryThe literal value true.
X-Webhook-CountBatched deliveryNumber of ping records in the batch.
POST /your-unguessable-kade-webhook-path HTTP/1.1
Content-Type: application/json
X-Webhook-Event: PING_RECEIVED
X-Webhook-ID: 4dce3817-df9c-48b3-a813-03af6bbb61f9
X-Webhook-Batch: true
X-Webhook-Count: 2

PING_RECEIVED

Filterable Optional batching

Fires when a persisted geolocation ping is flushed for a workspace and matches the subscription filters. Single and batched payloads have intentionally different top-level shapes.

Single delivery has no event envelope. A non-batched delivery is the full persisted PingLog record itself. A batched delivery wraps ping records in { event, count, pings }. Do not look for a top-level event property on a single ping.

Key operator-facing PingLog fields

The ping record is an internal persistence entity and can contain more columns or loaded relations than this table. The fields below are the key values currently consumed by operators. This is not a promise that every database field or internal relation is a stable public schema.

FieldTypeMeaning
idnumberPersisted ping identifier.
clientUserIdstring or nullYour user or player identifier associated with the ping.
ipstring or nullObserved client IP address.
latitude / longitudenumberEvaluated location coordinates.
accuracynumber or nullLocation accuracy in meters when available.
insidebooleanWhether the location was inside the evaluated area.
statusstring or nullPersisted ping outcome status.
riskScorenumberComputed Kade risk score.
reasonCodesstring[] or nullRisk and policy reason codes.
isIpBasedLocationbooleanWhether the evaluated location came from IP geolocation.
gadmCountry / gadmProvince / gadmCitystring or nullResolved administrative geography.
geohash6 / geohash7string or nullResolved location cell identifiers.
platformweb | ios | androidSDK platform that generated the ping.
sourcestring or nullCaller-supplied source label.
deviceTypemobile | tablet | desktop | nullCoarse device form factor.
os / osVersionstring or nullNormalized operating system details.
isVpn / isProxy / isTor / isDatacenter / isCloudboolean or nullDenormalized network-risk flags.
timestamptimestampTime the ping record was created.

Representative single payload

{
  "id": 781245,
  "clientUserId": "player-8472",
  "ip": "203.0.113.42",
  "latitude": 51.5074,
  "longitude": -0.1278,
  "accuracy": 18.4,
  "inside": true,
  "status": "PASS",
  "riskScore": 12,
  "reasonCodes": [],
  "isIpBasedLocation": false,
  "gadmCountry": "United Kingdom",
  "gadmProvince": "England",
  "gadmCity": "Greater London",
  "geohash6": "gcpvj0",
  "geohash7": "gcpvj0d",
  "platform": "web",
  "source": "checkout",
  "deviceType": "desktop",
  "os": "Mac OS",
  "osVersion": "15.5",
  "isVpn": false,
  "isProxy": false,
  "isTor": false,
  "isDatacenter": false,
  "isCloud": false,
  "timestamp": "2026-08-04T10:45:18.511Z"
}

Representative batched payload

{
  "event": "PING_RECEIVED",
  "count": 2,
  "pings": [
    {
      "id": 781245,
      "clientUserId": "player-8472",
      "latitude": 51.5074,
      "longitude": -0.1278,
      "inside": true,
      "riskScore": 12,
      "platform": "web",
      "timestamp": "2026-08-04T10:45:18.511Z"
    },
    {
      "id": 781246,
      "clientUserId": "player-8473",
      "latitude": 51.5081,
      "longitude": -0.1291,
      "inside": false,
      "riskScore": 76,
      "platform": "ios",
      "timestamp": "2026-08-04T10:45:18.812Z"
    }
  ]
}

Batched subscriptions group matched pings from the current flush and split them into chunks of at most 200. A batch may therefore contain fewer than 200 records. URL subscriptions can choose batching. Slack PING_RECEIVED subscriptions are always forced to batched mode.

BLACKLIST_CHANGED

Single payload

Despite its legacy name, this event fires on any client status change, not only blacklisting or unblacklisting. It is emitted only when the old and new statuses differ.

FieldTypeMeaning
clientIdstringKade client UUID.
clientUserIdstring or nullLast user or player identifier seen for the client.
oldStatusstringStatus before the change.
newStatusstringStatus after the change.
timestamptimestampTime Kade applied the status change.
backofficeUrlstring, optionalWorkspace backoffice link when an absolute URL can be built.
{
  "clientId": "a8f56280-0f35-4adb-8b0a-524f127d00b2",
  "clientUserId": "player-8472",
  "oldStatus": "active",
  "newStatus": "review",
  "timestamp": "2026-08-04T10:47:03.180Z",
  "backofficeUrl": "https://your-workspace.example/verified-backoffice-path"
}

This event has no event-specific filters and is delivered as a single payload. The example backoffice URL is illustrative only and is not a documented Kade API base URL.

CLUSTER_DETECTED

Single payload

Fires when a geographic or IP cluster first crosses its configured distinct-client threshold, then fires again when cumulative growth crosses each 50 percent milestone relative to the initial baseline.

FieldTypeMeaning
kindgeo7 | geo6 | ipCluster cell type.
keystringGeohash or IP value identifying the cluster.
workspaceIdnumberWorkspace that owns the detection.
clientCountnumberCumulative distinct clients in the open detection.
windowMsnumberConfigured rolling detection window in milliseconds.
firstSeentimestamp or nullEarliest observation when available.
lastSeentimestampLatest observation represented by this fire.
clientIdsstring[]Client UUID sample, capped at 500 entries.
clientUserIdsstring[]Known user or player identifiers in the sample.
truncatedbooleanWhether the full client set exceeded the retained sample.
reasoninitial | growthInitial threshold crossing or later growth milestone.
baselineCountnumberDistinct client count at initial detection.
milestonePctnumber100 initially, then 150, 200, 250, and so on.
backofficeUrlstring, optionalCluster Monitor deep link when available.
{
  "kind": "geo7",
  "key": "gcpvj0d",
  "workspaceId": 42,
  "clientCount": 18,
  "windowMs": 3600000,
  "firstSeen": "2026-08-04T09:54:00.000Z",
  "lastSeen": "2026-08-04T10:49:22.000Z",
  "clientIds": [
    "a8f56280-0f35-4adb-8b0a-524f127d00b2",
    "8e14c91e-cc83-469d-af09-d8e4d2789365"
  ],
  "clientUserIds": ["player-8472", "player-9021"],
  "truncated": false,
  "reason": "growth",
  "baselineCount": 12,
  "milestonePct": 150,
  "backofficeUrl": "https://your-workspace.example/verified-cluster-path"
}

Cluster subscriptions have no event-specific filters. They use the generic single-payload path and are not batched.

EMAIL_FLAGGED

Filterable Single payload

Fires only for recorded live checks whose verdict is high or review. Dry runs do not record or dispatch, and retro scans do not dispatch webhooks.

FieldTypeMeaning
eventEMAIL_FLAGGEDEvent name included in this payload.
emailstringEmail address evaluated by the live check.
firstNamestring or nullFirst name supplied with the check.
lastNamestring or nullLast name supplied with the check.
playerIdstring or nullPlayer identifier supplied with the check.
scorenumber or nullModel score when available. Deterministic rules can flag with no score.
verdicthigh | reviewFlagged verdict.
patternFamilystring or nullDetected pattern family when one applies.
reasonsstring[]Rules and signals supporting the verdict.
checkedAttimestampTime used for the live check.
backofficeUrlstring, optionalEmail Checks deep link when available.
{
  "event": "EMAIL_FLAGGED",
  "email": "maria.1987@example.test",
  "firstName": "Maria",
  "lastName": "Silva",
  "playerId": "player-9021",
  "score": 0.97,
  "verdict": "high",
  "patternFamily": "sequential_suffix",
  "reasons": [
    "numeric_suffix_sequence",
    "domain_registration_velocity"
  ],
  "checkedAt": "2026-08-04T10:51:04.303Z",
  "backofficeUrl": "https://your-workspace.example/verified-email-checks-path"
}

Filters support minScore, verdicts, and patternFamilies, with all configured conditions combined using AND. A deterministic-rule detection with score: null bypasses minScore because there is no model score to compare. It must still satisfy any verdict and pattern-family filters.

HIGH_RISK_COUNTRY_PING

Filterable Single payload

Fires once per persisted ping whose resolved country is in the workspace risk-country list. IP-based locations are included and explicitly identified in the payload.

FieldTypeMeaning
eventHIGH_RISK_COUNTRY_PINGEvent name included in this payload.
clientIdstring or nullKade client UUID when associated.
clientUserIdstring or nullYour user or player identifier.
countrystringResolved country that matched the risk list.
gadmProvincestring or nullResolved province or first-level region.
gadmCitystring or nullResolved city or local administrative region.
platformweb | ios | androidSDK platform.
sourcestring or nullCaller-supplied source label.
riskScorenumberComputed Kade risk score.
latitude / longitudenumberEvaluated coordinates.
isIpBasedLocationbooleanWhether coordinates came from IP geolocation.
labelAddedbooleanWhether this processing pass newly added the high-risk-country label.
timestamptimestampPersisted ping timestamp.
backofficeUrlstring, optionalWorkspace backoffice link when available.
{
  "event": "HIGH_RISK_COUNTRY_PING",
  "clientId": "a8f56280-0f35-4adb-8b0a-524f127d00b2",
  "clientUserId": "player-8472",
  "country": "Example Risk Country",
  "gadmProvince": "Central Province",
  "gadmCity": "Capital District",
  "platform": "android",
  "source": "login",
  "riskScore": 84,
  "latitude": 35.6895,
  "longitude": 139.6917,
  "isIpBasedLocation": false,
  "labelAdded": true,
  "timestamp": "2026-08-04T10:53:11.928Z",
  "backofficeUrl": "https://your-workspace.example/verified-backoffice-path"
}

Filters support platforms and sources. Conditions combine using AND; each absent or empty list matches all values. Delivery remains one payload per matched ping and is not batched.

Filter reference

Filters are stored on a subscription in the filters object. Every configured field must match. Empty or omitted filters match all events of that subscription's type.

PING_RECEIVED filters

FieldAccepted valueMatching behavior
outcomepass | outside | blockedMatches the derived operator outcome.
riskScoreMin / riskScoreMaxintegerInclusive lower and upper score bounds.
reasonCodesstring[]All selected codes must match. Bare codes also match subtype variants.
geohashPrefixesstring[]Any prefix may match the ping's geohash7.
vpnFlagsisVpn, isProxy, isTor, isDatacenter, isCloudEvery selected flag must be true.
gadmCountry / gadmProvince / gadmCitystringExact administrative-area match.
regionIdintegerExact Kade region ID.
platformweb | ios | androidExact SDK platform.
deviceTypemobile | tablet | desktopExact form factor.
osstringExact normalized operating system.
osVersionstringPrefix match against the OS version.
userIp / clientUserId / sourcestringExact value match.
labelsstring[]Client must have every manual label. Manual labels are normalized.
analysisLabelsstring[]Client must have every canonical analysis label.
{
  "outcome": "blocked",
  "riskScoreMin": 70,
  "platform": "web",
  "reasonCodes": ["LOCATION_GPS_SPOOFING"],
  "analysisLabels": ["risk:high-risk-country"]
}

Other event filters

EMAIL_FLAGGED

minScore from 0 to 1, verdicts as strings, and patternFamilies as strings.

HIGH_RISK_COUNTRY_PING

platforms accepts web, ios, and android. sources accepts source strings.

BLACKLIST_CHANGED and CLUSTER_DETECTED currently have no event-specific filters.

Slack destinations

A subscription can deliver either to a URL or to a Slack channel connected to the workspace. Slack delivery renders a message and calls Slack directly; it does not POST the JSON payload to your endpoint.

Destination fields

Set destinationType to SLACK and provide slackChannelId. A slackChannelName can be retained for display.

Message templates

messageTemplate controls a single message or each row in a ping batch. messageHeaderTemplate controls the header for batched PING_RECEIVED messages. Null uses the event default.

Slack PING_RECEIVED is always batched. The server forces batched: true for this combination even when the create or update request asks for false. This protects the destination from one Slack message per ping. Other event types continue through their single-payload paths.

Fetch GET /webhooks/template-variables to obtain the server-provided variable catalog and event defaults rather than hard coding a variable list.

Webhook management API

These endpoints require a Kade JWT in the Authorization: Bearer header and operate in the authenticated user's workspace. Supply your verified Kade API origin as KADE_API_ORIGIN. This guide intentionally does not invent a public base URL or undocumented response schemas.

Permissions: Read operations require webhooks:read. Create, update, and delete operations require webhooks:manage.
GET /webhooks/template-variables

Returns the server's template-variable catalog and per-event default templates. Requires webhooks:read.

curl "$KADE_API_ORIGIN/webhooks/template-variables" \
  -H "Authorization: Bearer $KADE_JWT"
POST /webhooks

Creates a subscription. Requires webhooks:manage. event is required. destinationType accepts URL or SLACK and defaults to URL behavior when omitted.

  • URL destination: provide a valid url.
  • Slack destination: provide slackChannelId; slackChannelName is optional.
  • batched and filters are optional and apply as documented above.
  • messageTemplate and messageHeaderTemplate are optional Slack templates.
curl -X POST "$KADE_API_ORIGIN/webhooks" \
  -H "Authorization: Bearer $KADE_JWT" \
  -H "Content-Type: application/json" \
  --data '{
    "destinationType": "URL",
    "url": "https://hooks.example.com/kade/unguessable-path",
    "event": "PING_RECEIVED",
    "batched": true,
    "filters": {
      "riskScoreMin": 70,
      "platform": "web"
    }
  }'
{
  "destinationType": "SLACK",
  "slackChannelId": "C0123456789",
  "slackChannelName": "risk-alerts",
  "event": "EMAIL_FLAGGED",
  "filters": {
    "verdicts": ["high"],
    "minScore": 0.95
  },
  "messageTemplate": "*{{verdict}}* email: `{{email}}`"
}
GET /webhooks

Lists subscriptions for the authenticated workspace, newest first. Requires webhooks:read.

curl "$KADE_API_ORIGIN/webhooks" \
  -H "Authorization: Bearer $KADE_JWT"
PATCH /webhooks/:id

Updates an existing subscription in the authenticated workspace. Requires webhooks:manage. The current update contract accepts url, slackChannelId, slackChannelName, isActive, batched, filters, messageTemplate, and messageHeaderTemplate.

Pass null for filters to clear filters, or for either template field to restore its event default. destinationType and event are selected at creation and are not fields in the current PATCH contract. Create a replacement subscription to change either one.

curl -X PATCH "$KADE_API_ORIGIN/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $KADE_JWT" \
  -H "Content-Type: application/json" \
  --data '{
    "isActive": true,
    "batched": true,
    "filters": {
      "platforms": ["ios", "android"],
      "sources": ["login"]
    }
  }'
DELETE /webhooks/:id

Deletes the subscription from the authenticated workspace. Requires webhooks:manage.

curl -X DELETE "$KADE_API_ORIGIN/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $KADE_JWT"

Planning a webhook rollout?

Talk to Kade about destination setup, event selection, filtering, Slack templates, and safe production handling.

Contact Kade