Lookup notifications

Get notified when a lookup finishes

Getting started has you poll GET /v1/profiles/{businessId} until it stops returning 202. If you'd rather not poll, register a URL once and Lumen Sieve calls it for you the moment each resolve completes.

Warning: This is a narrow, single-purpose mechanism — not a general eventing platform. It fires exactly twice per lookup at most (once on success, once on failure) and covers only POST /v1/profiles/lookup completions. It does not cover ongoing monitoring of a business for later changes — that's a separate, not-yet-released feature.

1. Register a URL

Requires the webhooks:manage scope. You choose which of the two events you want — most integrations want both.

cURL
bash
curl -X POST https://api.lumen.com/v1/webhooks/subscriptions \
  -H "Authorization: Bearer lumen_test_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example.com/lumen/webhook",
    "event_types": ["profile.resolved", "profile.failed"]
  }'
Response — 201 Created
json
{
  "secret": "whsec_5f2a...c19b",
  "subscription": {
    "id": "sub_...",
    "url": "https://yourapp.example.com/lumen/webhook",
    "event_types": ["profile.resolved", "profile.failed"],
    "enabled": true,
    "created_at": "2026-08-01T00:00:00Z"
  }
}
Warning: secret is shown exactly once, the same as an API key. Store it — you need it to verify deliveries (below).

2. What you receive

When a lookup you started finishes, Lumen Sieve sends one POST to your URL — success or failure, never both for the same lookup:

POST to your URL — profile.resolved
json
{
  "event_id": "evt_...",
  "event_type": "profile.resolved",
  "business_id": "616de6d1-7732-4ece-a551-5e3187923aa8",
  "customer_id": "acc_...",
  "profile_id": "ebbbe097-d475-4051-b358-8c638c85b32c",
  "identifier": "Blispa Barbershop & Spa",
  "status": "resolved",
  "resolved_at": "2026-08-01T00:30:38Z"
}

A profile.failed event carries the same shape with status: "failed" and an error field instead of profile_id. Either way, fetch GET /v1/profiles/{business_id} to get the actual profile — the event is just the notification, not the payload.

3. Verify the signature

Every delivery carries an X-LUMEN-Signature header: t=<unix timestamp>,v1=<hex HMAC-SHA256>, computed over the raw request body using the secret from step 1. Recompute it yourself and compare — never trust a delivery without checking this.

Python
python
import hashlib, hmac, time

def verify(payload_bytes: bytes, signature_header: str, secret: str, tolerance_seconds: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    timestamp, signature = parts["t"], parts["v1"]
    if abs(time.time() - int(timestamp)) > tolerance_seconds:
        return False  # too old — possible replay
    expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Retries and failed deliveries

If your endpoint doesn't respond with a 2xx, Lumen Sieve retries with backoff up to a configured attempt limit before giving up. Everything is inspectable and replayable after the fact:

EndpointPurpose
GET /v1/webhooks/deliveriesList recent delivery attempts across all your subscriptions
GET /v1/webhooks/deliveries/{id}Inspect one delivery — status, response, attempt count
POST /v1/webhooks/deliveries/{id}/retryManually retry one delivery on demand
GET /v1/webhooks/dead-letterList deliveries that exhausted all retries
POST /v1/webhooks/dead-letter/{id}/replayRe-attempt a dead-lettered delivery
POST /v1/webhooks/testSend a synthetic test event to a URL before going live

Managing your subscription

EndpointPurpose
GET /v1/webhooks/subscriptionsList your registered URLs
DELETE /v1/webhooks/subscriptions/{id}Remove a URL — no more deliveries to it
Tip: Polling still works after you register a URL — they're not mutually exclusive. A common pattern is: rely on the notification, but poll as a fallback if you haven't heard back after a few minutes.