LendWorksLendWorksDocs

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-webhooks
const 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

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint URL to receive events
eventsstring[]YesArray of event types to subscribe to
secretstringNoCustom 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-webhooks
curl -H "Authorization: Bearer lw_at_xxxxxxxxxxxxxxxx" \
     https://api.lend.works/v1/app-webhooks

Response

{
  "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/:id
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({
    events: ['lead.created', 'deal.funded', 'application.approved'],
    isActive: true,
  }),
})

Request Body

FieldTypeRequiredDescription
urlstringNoUpdated endpoint URL
eventsstring[]NoUpdated event types
isActivebooleanNotrue to enable, false to pause

Delete a Subscription

DELETE /v1/app-webhooks/:id
curl -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

HeaderDescription
X-Webhook-IdUnique delivery ID — use for deduplication
X-Webhook-TimestampUnix timestamp of when the event was sent
X-Webhook-SignatureHMAC-SHA256 signature (sha256=<hex> format)

Verifying with the SDK

npm install @lendworks/webhook-verify
import { 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:

EventDescription
lead.createdNew lead submitted
lead.updatedLead details modified
lead.stage_changedLead moved to a new pipeline stage
lead.assignedLead assigned to a broker
application.submittedApplication submitted to lender
application.approvedLender approved the application
application.declinedLender declined the application
deal.fundedDeal marked as funded
deal.commission_paidCommission recorded for a deal
contact.createdNew contact created
contact.updatedContact details modified
document.uploadedDocument uploaded to a record
app.installedYour app was installed by an org
app.uninstalledYour app was uninstalled

Use the event catalog endpoint to browse all available event types:

curl https://api.lend.works/v1/webhooks/catalog

Retry Behavior

Failed deliveries (non-2xx responses or timeouts after 10 seconds) are retried with exponential backoff:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
51 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 200 immediately, then process asynchronously. Deliveries time out after 10 seconds.
  • Deduplicate — Use X-Webhook-Id to 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