Light AIDocs Beta Open Light AI

Webhooks & events

Webhooks

Signed, retried and replayable event delivery to your HTTPS endpoint, compatible with Zapier, Make and n8n.

Light AI sends an HTTP POST to your endpoint for each event you subscribe to (27 types, see the event catalog). Endpoints are created in Settings → Developers → Webhooks, or through the API with the opt-in webhooks:manage scope.

Subscribe#

curl -X POST "https://api.light-ai.app/v1/webhook_endpoints" \
  -H "Authorization: Bearer $LIGHT_AI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://hooks.example.com/light-ai",
  "events": [
    "lead.created",
    "quote.approved"
  ],
  "description": "CRM sync"
}'
  • url: HTTPS on port 443 or 8443, a public host. Light AI never follows redirects.
  • events: the types to receive, or ["*"] for every type your scopes can read.
  • secret (whsec_…) is returned once. Subscribing the same URL and events again returns the same endpoint with a fresh secret; the old one keeps working for 24 hours (REST-hook friendly).
  • static_header: an optional fixed header (for n8n Header Auth), stored encrypted.
  • exclude_own_origin: skip events caused by this key's own writes. Default false.

The request#

HeaderValue
webhook-idThe event id (evt_…), the same on every retry and replay. De-duplicate on it.
webhook-timestampUnix seconds when this attempt was signed.
webhook-signatureSpace-separated v1,<base64 HMAC-SHA256> over {id}.{timestamp}.{body}. See Verify signatures.
Light-AI-Event-TypeThe event type, e.g. quote.approved.
Light-AI-DeliveryThe delivery id (whd_…), for the delivery log.
Light-AI-OrganizationThe contractor organization (org_…).

The envelope#

data.object is the object as it is when the delivery is sent (rendered at delivery time), data.previous_attributes holds changed fields for updates, and origin says who caused the event: app, embed (website form), portal (the homeowner), api, inbound or system. Partner deliveries carry grant with your external_reference.

lead.createdJSON
{
  "id": "evt_3f9c2a7be41d…",
  "object": "event",
  "api_version": "2026-11-01",
  "created": "2026-10-12T14:03:11.000Z",
  "livemode": false,
  "organization": {
    "id": "org_3f9c2a7be41d…",
    "name": "Dana Reyes"
  },
  "grant": {
    "id": "grant_3f9c2a7be41d…",
    "external_reference": "string",
    "metadata": {}
  },
  "origin": {
    "type": "app",
    "principal_id": "string",
    "source_system": "string",
    "external_ref": {
      "system": "string",
      "namespace": "string",
      "id": "string"
    }
  },
  "type": "lead.created",
  "data": {
    "object": {
      "id": "lead_3f9c2a7be41d…",
      "object": "lead",
      "livemode": false,
      "status": "new",
      "held": true,
      "needs_address": true,
      "source": "website_widget",
      "source_detail": "string",
      "contact": {
        "first_name": "Dana",
        "last_name": "Reyes",
        "email": "dana@example.com",
        "email_raw": "string",
        "phone": "+15125550134",
        "phone_raw": "string",
        "phone_verified": true
      },
      "address": {
        "line1": "4102 Oak Hollow Dr",
        "line2": "string",
        "city": "Austin",
        "region": "TX",
        "postal_code": "78731",
        "country": "US",
        "formatted": "string",
        "lat": 0,
        "lng": 0
      },
      "service_interest": "christmas_lights",
      "service_interest_raw": "string",
      "message": "string",
      "attribution": {},
      "consent": {},
      "custom_fields": {},
      "notes": "string",
      "status_reason": "string",
      "client_id": "cli_3f9c2a7be41d…",
      "property_id": "prop_3f9c2a7be41d…",
      "possible_client_match": {
        "client_id": "cli_3f9c2a7be41d…"
      },
      "project": {
        "id": "prj_3f9c2a7be41d…",
        "status": "intake",
        "pipeline_stage": "request",
        "app_url": "https://example.com/…"
      },
      "form": {
        "id": "form_3f9c2a7be41d…",
        "name": "Dana Reyes"
      },
      "mockup_status": "not_requested",
      "form_answers": [
        {
          "key": "surfaces",
          "question": "What should we light?",
          "group": "lighting",
          "type": "multi_choice",
          "value": [
            "eaves",
            "garage"
          ],
          "labels": [
            "Roof peaks + gutters",
            "Garage doors"
          ]
        }
      ],
      "preview_looks": [
        "warm_white"
      ],
      "offer": {
        "template": "black_friday",
        "headline": "Black Friday: 20% off installs",
        "discount_type": "percent",
        "discount_percent": 20,
        "discount_amount": null,
        "label": "20% off",
        "ends_at": "2026-11-30T05:59:00Z",
        "book_by": "Book by Dec 1 for install before Christmas",
        "captured_at": "2026-11-27T14:03:11Z"
      },
      "assets": {
        "photos": [
          {
            "id": "string",
            "url": "https://example.com/…",
            "expires_at": "2026-10-12T14:03:11.000Z"
          }
        ],
        "mockups": [
          {
            "id": "string",
            "url": "https://example.com/…",
            "expires_at": "2026-10-12T14:03:11.000Z",
            "look": "warm_white"
          }
        ]
      },
      "external_refs": [
        {
          "system": "string",
          "namespace": "string",
          "id": "string",
          "owner": "self",
          "read_only": true
        }
      ],
      "touch_count": 1,
      "last_touched_at": "2026-10-12T14:03:11.000Z",
      "submitted_at": "2026-10-12T14:03:11.000Z",
      "created_at": "2026-10-12T14:03:11.000Z",
      "updated_at": "2026-10-12T14:03:11.000Z",
      "replayed": true,
      "fixture": true
    },
    "previous_attributes": {}
  }
}

Respond fast#

Answer any 2xx within 10 seconds, then process asynchronously. A 410 Gone disables the endpoint at once. Anything else (or a timeout) is retried.

Delivery semantics#

  • At least once. The same event can arrive twice (a retry after a timeout, a replay), always with the same webhook-id.
  • Not ordered. Compare data.object.updated_at (and data.stage_change.sequence for project.stage_changed) with what you stored, skip older ones, and upsert by data.object.id.
  • Large quotes (over 256 KB) arrive with truncated: true and empty line_items: read the full quote with GET /v1/quotes/{id}.

Retries#

A failed attempt is retried 10 times: after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, 12 hours, then 24 hours three times, each ±20% jitter (about 4 days in all). Then the delivery is abandoned.

An endpoint with no success for 72 hours and at least 20 failures is disabled. The owners get an e-mail, and endpoints that subscribe to webhook_endpoint.disabled get that event.

Logs, retry and replay#

  • Deliveries are logged for 30 days: GET /v1/webhook_endpoints/{id}/deliveries and Settings → Developers.
  • POST /v1/webhook_deliveries/{id}/retry sends one delivery again now.
  • POST /v1/webhook_endpoints/{id}/replay re-queues up to 5,000 events from the last 30 days (by default only those not yet delivered, including events missed while the endpoint was disabled). Replays reuse the original webhook-id.
curl -X POST "https://api.light-ai.app/v1/webhook_endpoints/we_…/replay" \
  -H "Authorization: Bearer $LIGHT_AI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "created_after": "2026-10-10T00:00:00Z",
  "types": [
    "quote.approved"
  ]
}'

Loops#

When your integration writes to Light AI and also listens to its events, set source_system on your writes (e.g. jobber) and skip events whose origin.source_system is yours, or set exclude_own_origin: true on the endpoint.

Security#

  • Always verify the signature; reject timestamps older than 5 minutes.
  • Endpoints cannot target private networks or Light AI hosts. Owners are e-mailed for every endpoint created through the API.
  • Payloads never contain image URLs or customer quote links unless the endpoint holds the opt-in scopes.