Webhook Subscriptions API
Programmatically manage webhook subscriptions for your app to receive real-time event notifications.
Overview
Apps can manage their webhook subscriptions through the API, allowing you to create, update, and delete subscriptions programmatically. Each subscription targets a specific URL and listens for a set of event types.
This API requires the webhooks:manage scope.
Create a Subscription
POST /v1/app-webhooksconst response = await fetch('https://api.lend.works/v1/app-webhooks', {
method: 'POST',
headers: {
Authorization: 'Bearer lw_at_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://yourapp.com/webhooks/lendworks',
events: ['lead.created', 'lead.stage_changed', 'deal.funded'],
}),
})
const { data } = await response.json()Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint URL to receive events |
events | string[] | Yes | Array of event types to subscribe to |
secret | string | No | Custom signing secret (auto-generated if omitted) |
Response
{
"data": {
"id": "whsub_550e8400-e29b-41d4-a716-446655440000",
"url": "https://yourapp.com/webhooks/lendworks",
"events": ["lead.created", "lead.stage_changed", "deal.funded"],
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"isActive": true,
"createdAt": "2026-03-15T10:00:00Z"
}
}Store the secret securely — it is only returned on creation and is used to verify webhook signatures.
List Subscriptions
GET /v1/app-webhookscurl -H "Authorization: Bearer lw_at_xxxxxxxxxxxxxxxx" \
https://api.lend.works/v1/app-webhooksResponse
{
"data": [
{
"id": "whsub_550e8400...",
"url": "https://yourapp.com/webhooks/lendworks",
"events": ["lead.created", "lead.stage_changed", "deal.funded"],
"isActive": true,
"createdAt": "2026-03-15T10:00:00Z"
}
],
"meta": {
"total": 1
}
}Update a Subscription
PATCH /v1/app-webhooks/:idawait fetch('https://api.lend.works/v1/app-webhooks/whsub_550e8400...', {
method: 'PATCH',
headers: {
Authorization: 'Bearer lw_at_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
events: ['lead.created', 'deal.funded', 'application.approved'],
isActive: true,
}),
})Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | Updated endpoint URL |
events | string[] | No | Updated event types |
isActive | boolean | No | true to enable, false to pause |
Delete a Subscription
DELETE /v1/app-webhooks/:idcurl -X DELETE \
-H "Authorization: Bearer lw_at_xxxxxxxxxxxxxxxx" \
https://api.lend.works/v1/app-webhooks/whsub_550e8400...Returns 204 No Content on success.
HMAC Signature Verification
Every webhook delivery includes signature headers for verification. Use the secret returned when you created the subscription.
Headers
| Header | Description |
|---|---|
X-Webhook-Id | Unique delivery ID — use for deduplication |
X-Webhook-Timestamp | Unix timestamp of when the event was sent |
X-Webhook-Signature | HMAC-SHA256 signature (sha256=<hex> format) |
Verifying with the SDK
npm install @lendworks/webhook-verifyimport { createVerifier } from '@lendworks/webhook-verify'
const verifier = createVerifier({
secret: process.env.WEBHOOK_SIGNING_SECRET,
})
// In your webhook handler
const event = verifier.verify(rawBody, {
'x-webhook-signature': req.headers['x-webhook-signature'],
'x-webhook-id': req.headers['x-webhook-id'],
'x-webhook-timestamp': req.headers['x-webhook-timestamp'],
})
console.log(event.event) // 'lead.created'
console.log(event.data) // { leadId: '...', orgId: '...' }Manual Verification
import crypto from 'node:crypto'
function verifyWebhook(
rawBody: string,
signature: string,
timestamp: string,
secret: string
): boolean {
// Reject timestamps older than 5 minutes
const now = Math.floor(Date.now() / 1000)
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false
}
const payload = `${timestamp}.${rawBody}`
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
// Signature format: "sha256=<hex digest>"
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
)
}Event Types
Common event types available for subscription:
| Event | Description |
|---|---|
lead.created | New lead submitted |
lead.updated | Lead details modified |
lead.stage_changed | Lead moved to a new pipeline stage |
lead.assigned | Lead assigned to a broker |
application.submitted | Application submitted to lender |
application.approved | Lender approved the application |
application.declined | Lender declined the application |
deal.funded | Deal marked as funded |
deal.commission_paid | Commission recorded for a deal |
contact.created | New contact created |
contact.updated | Contact details modified |
document.uploaded | Document uploaded to a record |
app.installed | Your app was installed by an org |
app.uninstalled | Your app was uninstalled |
Use the event catalog endpoint to browse all available event types:
curl https://api.lend.works/v1/webhooks/catalogRetry Behavior
Failed deliveries (non-2xx responses or timeouts after 10 seconds) are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 1 hour |
After 5 failed attempts, the event is moved to a dead letter queue.
Circuit Breaker
If a subscription accumulates 50 consecutive failures, it is automatically paused. Your app receives an app.webhook_paused lifecycle event (at any other active endpoint), and you can re-enable the subscription via the API:
await fetch('https://api.lend.works/v1/app-webhooks/whsub_550e8400...', {
method: 'PATCH',
headers: {
Authorization: 'Bearer lw_at_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({ isActive: true }),
})Best Practices
- Always verify signatures — Never process unverified webhook payloads
- Respond quickly — Return
200immediately, then process asynchronously. Deliveries time out after 10 seconds. - Deduplicate — Use
X-Webhook-Idto handle potential duplicate deliveries - Subscribe selectively — Only subscribe to events your app needs to reduce noise
- Monitor delivery health — Check subscription status periodically to catch paused endpoints