Webhooks Overview
LedgerCore accepts inbound webhooks from trusted partners so that activity performed outside the REST gateway still flows into our unified history, credit system, and subscription model. All webhooks are idempotent and authenticated at the edge.
Today LedgerCore only processes inbound webhooks (third-party → gateway). Outbound subscriptions (gateway → your URL) are on the roadmap — until then, use the Realtime API for push notifications.
Registered endpoints
Activity
Log KYT / KYA / KYE activity from async workers back into user history.
Clerk
User lifecycle + billing / subscription events from our auth provider.
Payments
PayPal and PayFast subscription payments that trigger credit grants.
Common patterns
All inbound webhooks share a few conventions:
- Endpoint convention: every webhook lives under
/api/webhook/<source>on the gateway. - HTTP semantics: always
POSTwithContent-Type: application/json. - Authentication: a shared secret header or a provider-signed payload. Unauthenticated
requests return
401immediately. - Idempotency: handlers de-duplicate by
event.id(or equivalent) so retrying the same webhook is safe. - Response contract:
2xxmeans "accepted, stop retrying".5xxor a timeout means the sender should retry with exponential backoff. - Latency budget: target under 2 s per request — webhooks run synchronously through the gateway middleware stack.
Security
All webhook secrets live in environment variables and are validated on every request. Rotate them regularly and never commit them to source control. Compromised secrets can be revoked instantly via the admin console.
| Webhook | Auth mechanism | Env var |
|---|---|---|
| Activity | X-Activity-Webhook-Secret header (shared) | ACTIVITY_WEBHOOK_SECRET |
| Clerk | Svix-style signature via X-Clerk-Webhook-Signature | CLERK_WEBHOOK_SECRET |
| PayPal | PayPal signature verification (PayPal SDK) | PAYPAL_WEBHOOK_ID |
| PayFast | MD5 signature over ordered params | PAYFAST_MERCHANT_KEY |
Testing locally
In development you can drive any endpoint with curl — just make sure the secret matches the
one configured in your .env:
curl -X POST http://localhost:8090/api/webhook/activity \
-H "Content-Type: application/json" \
-H "X-Activity-Webhook-Secret: $ACTIVITY_WEBHOOK_SECRET" \
-d '{
"activityType": "kyt",
"userId": "user_32T5kyEywX9x8X3P3XGxcyptIbn",
"statusCode": 200,
"blockchain": "bitcoin",
"transactionHash": "abc123..."
}'
Successful posts return:
{ "success": true, "id": "uuid-of-created-activity", "requestId": "…" }