Webhooks API¶
Register webhook subscriptions and manage event delivery.
Base permission: IsAdminOrAbove
Endpoints¶
| Method | Path | Description | Permission |
|---|---|---|---|
GET |
/webhooks |
List subscriptions | IsAdminOrAbove |
GET |
/webhooks/{id} |
Get subscription | IsAdminOrAbove |
POST |
/webhooks |
Create subscription | IsAdminOrAbove |
PUT |
/webhooks/{id} |
Update subscription | IsAdminOrAbove |
DELETE |
/webhooks/{id} |
Delete subscription | IsAdminOrAbove |
POST |
/webhooks/{id}/retry-delivery |
Retry failed delivery | IsAdminOrAbove |
GET |
/webhooks/{id}/dead-letters |
List failed deliveries | IsAdminOrAbove |
POST |
/webhooks/{id}/re-enable |
Re-enable disabled subscription | IsAdminOrAbove |
Create Subscription¶
{
"url": "https://example.com/webhooks/lms",
"events": ["loan.created", "loan.status_changed", "payment.received"],
"secret": "whsec_abc123...",
"is_active": true
}
Warning
Webhook URLs must use HTTPS. HTTP URLs are rejected at creation time.
Update Subscription¶
{
"events": ["loan.created", "loan.status_changed", "payment.received", "payment.reversed"],
"is_active": true
}
Event Types¶
| Event | Trigger |
|---|---|
loan.created |
New loan created |
loan.status_changed |
Loan status transition |
loan.disbursed |
Loan funded |
payment.received |
Payment completed |
payment.reversed |
Payment reversed |
payment.failed |
Payment processing failed |
delinquency.changed |
DPD bucket transition |
fee.assessed |
Fee assessed on loan |
modification.completed |
Modification applied |
compliance.failed |
Compliance check failed |
document.generated |
Document generated |
Payload Format¶
All events follow a standard envelope:
{
"event_type": "payment.received",
"timestamp": "2026-01-15T14:30:00Z",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"payment_id": "123e4567-e89b-12d3-a456-426614174000",
"loan_id": "789e0123-e89b-12d3-a456-426614174000",
"amount": "500.00"
}
}
Security¶
HMAC Verification¶
Every delivery includes an HMAC-SHA256 signature:
Verify in your endpoint:
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Delivery & Retry¶
Retry Policy¶
Failed deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 10 seconds |
| 2nd retry | 60 seconds |
| 3rd retry | 300 seconds (5 minutes) |
Maximum 3 retry attempts per delivery.
Dead Letters¶
After 5 consecutive failures, the subscription is auto-disabled:
is_activeset tofalselast_errorrecords the failure reason- Use
POST /webhooks/{id}/re-enableto reactivate after fixing the endpoint
Retry Failed Delivery¶
Manually retry a specific failed delivery:
List Dead Letters¶
View all failed deliveries for a subscription:
Response:
[
{
"id": "...",
"event_type": "payment.received",
"payload": { "..." },
"response_status": 500,
"response_body": "Internal Server Error",
"attempted_at": "2026-01-15T14:30:00Z",
"retry_count": 3
}
]
See Also¶
- Webhook Events --- Complete event type reference
- Cross-Module Communication --- How events are emitted