LedgerCore
Webhooks

Activity Webhook

Post KYT, KYA, or KYE activity records to the gateway from async workers that don't go through the REST path. Each post inserts one row into user_activity_history and appears immediately in the user's dashboard.

Endpoint

POST /api/webhook/activity

Authentication

Send the shared secret in an X-Activity-Webhook-Secret header. Requests without a matching header return 401 Unauthorized.

X-Activity-Webhook-Secret: $ACTIVITY_WEBHOOK_SECRET

Configure the secret via the ACTIVITY_WEBHOOK_SECRET environment variable. Generate one with:

openssl rand -hex 32

Request body

{
  "activityType": "kyt",
  "userId": "user_32T5kyEywX9x8X3P3XGxcyptIbn",
  "requestId": "a9b2-...-c1d3",
  "responseId": "f5a0-...-9e1b",
  "statusCode": 200,
  "blockchain": "bitcoin",
  "transactionHash": "3a1bfd9d...",
  "address": null,
  "entityId": null,
  "entityName": null,
  "riskLevel": "low",
  "riskScore": 12,
  "requestPayload": { "chain": "bitcoin", "txid": "3a1bfd9d..." },
  "responsePayload": { "riskLevel": "low", "riskScore": 12, "signals": [] },
  "responseSummary": { "risk": "low" },
  "errorMessage": null,
  "metadata": { "source": "async-worker-1" },
  "clientAppId": "app_2xMn1psnHkYRTH00nxdvp02IWfr",
  "clientAppName": "LedgerLink Web"
}

Field reference

FieldTypeRequiredDescription
activityType"kyt" | "kya" | "kye"Which engine produced the activity.
userIdstringClerk user ID the activity belongs to.
requestIdstring (UUID)Defaults to a fresh UUID if omitted.
responseIdstring (UUID)Upstream engine's response id, if available.
statusCodenumberHTTP status. Defaults to 200. Anything >= 400 is flagged as an error.
blockchainstringChain name (bitcoin, ethereum, solana, …). Required for KYT / KYA.
transactionHashstringRequired when activityType === "kyt".
addressstringRequired when activityType === "kya".
entityIdstringRequired when activityType === "kye".
entityNamestringHuman-readable label for KYE entries.
riskLevel"low" | "medium" | "high" | "critical"Surfaced on dashboards.
riskScorenumber (0–100)Machine-readable risk score.
requestPayloadobjectFull request body from your worker — useful for replay.
responsePayloadobjectFull upstream response — surfaced in the activity detail drawer.
responseSummaryobjectCompact version for list views.
errorMessagestringPopulated automatically for statusCode >= 400 if omitted.
metadataobjectFree-form; indexed as JSONB.
clientAppIdstringWhich application initiated the call (maps to app_* ids).
clientAppNamestringDisplay name for the app (e.g. "LedgerLink Web").

Responses

StatusBodyWhen
201{ "success": true, "id": "…", "requestId": "…" }Activity persisted.
400{ "error": "Invalid activityType. Must be one of: kyt, kya, kye" }Unknown activityType.
400{ "error": "userId is required" }Missing userId.
401{ "error": "Unauthorized" }Bad / missing secret.
500{ "error": "Failed to record activity" }Database error — safe to retry.

Example

Submit a KYA entry for an address lookup worker:

curl -X POST http://localhost:8090/api/webhook/activity \
  -H "Content-Type: application/json" \
  -H "X-Activity-Webhook-Secret: $ACTIVITY_WEBHOOK_SECRET" \
  -d '{
    "activityType": "kya",
    "userId": "user_32T5kyEywX9x8X3P3XGxcyptIbn",
    "statusCode": 200,
    "blockchain": "bitcoin",
    "address": "1Drt3c8pSdrkyjuBiwVcSSixZwQtMZ3Tew",
    "riskLevel": "medium",
    "riskScore": 42
  }'

Downstream effects

Writing an activity record does not touch credits — credit accounting happens inline on the REST path. Use the webhook purely to make async/off-gateway work visible to the user.

Was this page helpful?