Subscribe
Manage subscriptions
Delivery timing
Events to a healthy endpoint are typically delivered within 1 minute of the triggering change. Actual delivery speed depends on your endpoint. Lofty delivers to each endpoint over a limited number of concurrent connections, so a slow handler queues its own events: an endpoint that responds in under a second keeps up with any volume, while one that takes several seconds per request can fall minutes behind at peak — see the throughput note under Best practices. Failed deliveries follow the retry schedule below. Deliveries to a disabled subscription pause and resume automatically after recovery — see Health probes.Retries and failure handling
A delivery counts as successful when your endpoint returns any 2xx status code. Any other response — an error status, a timeout, or a connection failure — is handled by the rules in this section.Retry schedule
Failed deliveries are retried up to 9 total attempts within 48 hours, with exponential backoff between attempts:
Each delay includes ±20% random jitter so retries from many events don’t arrive in synchronized bursts. An event that still cannot be delivered 48 hours after it was created is discarded.
Response handling
Idempotent delivery
Every delivery request carries aLofty-Webhook-Id header. Its value is unique per event and stable across retries. If a timeout caused your response to be lost, the retry arrives with the same Lofty-Webhook-Id — use it to deduplicate instead of processing the event twice.
Endpoint health and automatic disabling
Lofty tracks delivery health per subscription and disables a subscription automatically when:- your endpoint returns 410 Gone — immediately, or
- deliveries fail continuously with no successes in between — within hours for high-volume endpoints, or after roughly a day of sustained failures otherwise.
Health probes and automatic recovery
Disabled subscriptions receive a ping probe approximately every 6 hours:Notification recipients
Which subscribed users receive a callback depends on the subscription’s delivery mode (permissionMode). In both modes, only users in the lead’s team who have subscribed to the event type are considered.
Assignment-based delivery — permissionMode: 0 (default)
A subscriber receives the callback only for leads assigned to them or that they administer:
- Hidden leads — only the assigned agent receives the notification.
- Other leads — the subscriber receives it if they are the assigned agent, or a Company Owner / Company Admin. All other subscribers are excluded.
Ownership-based delivery — permissionMode: 1
A subscriber receives the callback for any lead they manage, regardless of who it is assigned to:
- Every assignment-based case above still applies (assigned agent, Company Owner / Company Admin).
- In addition, the subscriber receives the callback for any lead within their ownership scope — owned by their team or office, or personally owned by them — even if it is assigned to someone else.
- Hidden leads are still delivered only to users who manage them.
permissionMode when creating the subscription; it defaults to 0, so existing subscriptions are unaffected.
Common fields
All payloads include:Event types
Event payloads
1. Agent Info
Triggered when an agent is created or updated.2. Lead Info
Triggered when a lead is created, updated, or deleted. Only the relevant action fields are present.3. Lead Activity
Triggered when a lead performs site activity (SiteBrowse, SiteFavorite, SiteSearch, etc.).4. Listing Alert
Triggered when a listing alert changes.5. Transaction
Triggered when a transaction is created, updated, or deleted. Only the relevant action fields are present.6. Call
Triggered on a call event.7. Email
Triggered on an email event.8. Text
Triggered on a text/SMS event.communicationType values —
MANUAL: agent-initiated communication. LOGGED: communication recorded after the fact. AUTO events are excluded from webhooks.9. Note
Triggered when a note is created, updated, or deleted. Only the relevant action fields are present.10. Task
Triggered when a task is created, updated, finished, or deleted. Multiple action fields may appear in a single payload.11. Appointment
Triggered when an appointment is created, updated, finished, or deleted. Multiple action fields may appear in a single payload.12. Pipeline Change
Triggered when a lead’s pipeline stage changes.Best practices
Respond immediately. Return a2xx before processing and move your logic to a background job. Lofty limits concurrent deliveries per endpoint, so a slow handler directly reduces your delivery throughput — at high event volume, synchronous processing is the most common cause of delayed webhooks.
Handle duplicates. The same event may be delivered more than once (for example, when a timeout swallowed your response). Deduplicate with the Lofty-Webhook-Id header — it is stable across retries of the same event.
Don’t assume ordering. Retries mean an older event can arrive after a newer one. Order by the updateTime field inside the payload, not by arrival time.
Acknowledge pings. Return 2xx to {"eventType": "ping"} probes — they are how a disabled subscription recovers automatically.
Decommission cleanly. When you shut an endpoint down for good, return 410 Gone so Lofty stops delivering immediately instead of retrying against a dead URL.
Validate payloads. Check that listId matches your expected event type before processing.
Use HTTPS. Plain HTTP endpoints are rejected.
Example receiver
Resolving communication events to full records
After receiving a Call / Email / Text webhook, fetch the full record by passing the event’stimelineId as the communicationId path parameter of the v2 by-ID endpoint:
timelineId is the recommended round-trip key — it always points to the same lead_timeline row regardless of whether the event is MANUAL or LOGGED. The legacy callId / emailId / textId fields still ship for backward compatibility, but their underlying record changes between the two communicationType values, so they are not safe to pass to the v2 by-ID endpoints.
All IDs in webhook payloads are 64-bit integers. JavaScript clients must handle them carefully — see JS / TS Integration.