REST API reference
Webhooks
Receive signed events when something changes in Levr.
Configure a destination
Ask a Portal administrator to add a destination in API settings → Webhooks. Enter a label, a public HTTPS receiver URL and the selected events.
Save the signing_secret shown at creation in your secret manager. It is the complete whsec_... string, not an API key or a base64-decoded value. Share it separately from the destination's copied implementation instructions. Existing destinations do not reveal the plaintext again.
Use the destination's Copy instructions, Send test event and Rotate secret controls to implement, test and rotate the receiver. Rotation reveals a new secret and the previous secret's expiry. The configured overlap defaults to 24 hours; use the returned expiry rather than assuming the default. During overlap Levr sends signatures for both secrets.
Choose events
Subscribe each destination only to the resource changes its integration needs. An event is created after the change commits in Levr, then delivered independently to every active destination that selected it.
business.createdAfter Levr saves the Business in your Portal.business.status_changedAfter Levr commits a change to the business's stage in this provider's business pipeline.business.updatedAfter Levr saves a change to the Business name or referral information in your Portal.client_intake_application_autofill_run.settledAfter Levr commits the terminal outcome of an asynchronous autofill Run.client_intake_application.createdAfter Levr commits the shared Client Intake Application.client_intake_application.status_changedAfter Levr commits a Client Intake Application status transition, including submission.client_intake_application.submittedAfter Levr commits a successful Client Intake Application submission.client_intake_application.updatedAfter Levr saves a change to the Client Intake Application answers.deal_application_autofill_run.settledAfter Levr commits the terminal outcome of an asynchronous autofill Run.deal_application.createdAfter Levr grants your Portal access to the Deal Application.deal_application.status_changedAfter Levr commits one Deal Application status transition.deal_application.submittedAfter Levr commits the shared Deal Application submission.deal_application.updatedAfter Levr saves a change to the Deal Application answers.deal.createdAfter Levr saves the new Deal.deal.stage_changedAfter Levr saves the Deal's new pipeline stage.deal.updatedAfter Levr commits a Deal edit, Project association, or shared application lifecycle change.project.createdAfter Levr saves the new Project.project_matchmaking_application_autofill_run.settledAfter Levr commits the terminal outcome of an asynchronous autofill Run.project_matchmaking_run.status_changedAfter a calculation settles as partially complete, completed, failed, or cancelled.project.stage_changedAfter Levr commits one Project stage transition.project.updatedAfter Levr saves a change to the Project or its Criteria answers.Implement the receiver
- Accept the raw request body. Do not parse or modify it before signature verification.
- Verify the signature. Compare
Levr-Signatureagainst the unmodified body and reject stale timestamps. - Return a successful response quickly. Queue longer work after acknowledging the event.
- Make processing idempotent. Delivery is at least once, so the same event can arrive more than once.
Request headers
| Header | Use |
|---|---|
Levr-Signature | Verify that the request came from Levr. |
Levr-Event-ID | Identify the immutable event across destinations. |
Levr-Delivery-ID | Identify this destination's delivery across retry attempts. |
Content-Type | Webhook requests use application/json. |
Accept signatures generated within 300 seconds of the current time.
Verify the signature
The header is comma-separated: t=UNIX_SECONDS,v1=LOWERCASE_HEX. Rotation can add another v1. Compute HMAC-SHA256 with the complete secret's UTF-8 bytes over the canonical decimal ASCII timestamp, one literal period and the unmodified request-body bytes. Compare the lowercase hexadecimal digest using a constant-time comparison. Accept if any configured secret matches any v1 and the timestamp is within 300 seconds in either direction.
import hashlib
import hmac
import time
def verify_levr(raw_body, header, secrets, now=None):
parts = [part.split("=", 1) for part in header.split(",") if "=" in part]
timestamps = [value for name, value in parts if name == "t"]
signatures = [value for name, value in parts if name == "v1"]
try:
timestamp = int(timestamps[0])
except (IndexError, ValueError):
return False
current = int(time.time()) if now is None else now
if abs(current - timestamp) > 300 or not signatures:
return False
signed = str(timestamp).encode("ascii") + b"." + raw_body
return any(
hmac.compare_digest(
hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest(), signature
)
for secret in secrets for signature in signatures
)Read bytes before JSON parsing; do not reconstruct JSON for verification. Levr emits one canonical integer timestamp. The current verifier uses the first t, parses it as an integer and signs its canonical decimal representation; it ignores unrecognized or malformed comma fragments. Reject a missing/unparseable timestamp, a missing or mismatched signature, and timestamps outside the tolerance. Receivers may reject duplicate timestamps and noncanonical header syntax more strictly. Check Levr-Event-ID against the verified body's id and deduplicate by that immutable event ID. Levr-Delivery-ID identifies one destination delivery and stays stable across retries.
Delivery and recovery
Return any 2xx response only after durable acceptance, within 10 seconds. Timeouts, connection failures and non-2xx responses retry, up to 8 total attempts. The scheduled delays are 60, 120, 240, 480, 960, 1920 and 3600 seconds; worker availability can delay actual arrival. Redirects are not followed.
Events can arrive out of order. Use the verified event as a signal, then read the current REST resource before updating your system. Inspect failed deliveries and attempts in your Portal. If retries end before your receiver accepts an event, fix the receiver and read the affected records through REST to bring your system up to date.
Use a Portal test event to check your receiver's signature verification and delivery handling. It follows the same delivery path and sets test_mode: true, without changing a Business, Project or Deal. Exclude test events from production record updates, then check a real resource change to verify your complete integration.
