Guides
Webhooks
Webhooks allow you to receive HTTP callbacks when events occur in your Liveday account. Instead of polling the API, you configure a URL and Liveday will send a POST request to it whenever a subscribed event fires.
All webhook events, payload schemas, and the delivery-inspection endpoints are also documented in the interactive API Reference under the Webhooks tag and the spec's webhooks section.
Getting started
- Create an endpoint — an HTTPS URL on your server that accepts
POSTrequests with a JSON body and responds with a2xxstatus code. - Register it — in the Integrations section of your Liveday dashboard, create a webhook integration, provide your URL, and select the events to subscribe to.
- Acknowledge quickly — return
2xxwithin 30 seconds. If processing takes longer, acknowledge first and process asynchronously. - Deduplicate — store the
X-Webhook-Delivery-IDheader value and skip deliveries you have already processed.
Events
| Event | Description | Payload |
|---|---|---|
OrderCompleted | Fired when an order is successfully completed for your organization | The complete order object — the same Order model returned by GET /analytics_search/api/transactions (see the API Reference) |
Delivery
When an event fires, Liveday sends an HTTP POST request to your configured URL with the event payload as JSON.
Headers
Every webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-Delivery-ID | Unique ID for this delivery — stable across retries of the same delivery |
X-Webhook-Event-Type | The event type (e.g. OrderCompleted) |
X-Webhook-Attempt | Attempt number (starts at 1, up to 5) |
Example payload (OrderCompleted, abridged)
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "anna@example.com",
"phone_number": "+46701234567",
"total_price": 59800,
"payment_method": "card",
"payment_method_brand": "visa",
"is_refunded": false,
"order_number": 10042,
"order_source": "webshop",
"created_at": "2026-03-01T14:30:00Z",
"cart": {
"organization": "your-org-id",
"event": "evt_summer2026",
"currency": "SEK",
"total_price": 59800,
"original_price": 69800,
"items": [
{
"product_type": "Ticket",
"name": "General Admission",
"quantity": 2,
"price": 29900,
"vat": 6.0,
"event": "evt_summer2026",
"event_name": "Summer Festival 2026",
"category": "ticket"
}
],
"campaigns": [
{
"uuid": "camp_earlybird",
"name": "Early Bird",
"code": "EARLY2026",
"discount_amount": 10000
}
]
}
}
The payload is the full Order object and contains more fields than shown here — the complete, always up-to-date schema is in the Webhooks section of the API Reference.
Responding
Your endpoint should return a 2xx status code to acknowledge receipt. Any other status code — or a timeout or connection error — is treated as a failed attempt and triggers a retry.
Respond quickly
Your endpoint must respond within 30 seconds (connection timeout: 5 seconds). If processing takes longer, acknowledge the webhook immediately and process it asynchronously.
Retries and delivery guarantees
| Aspect | Behavior |
|---|---|
| Success | Any 2xx response within 30 seconds |
| Failure | Non-2xx response, timeout (30 s total / 5 s connect), or connection error |
| Attempts | Up to 5 attempts in total (1 initial + up to 4 retries) |
| Retry timing | Failed deliveries are re-queued immediately — a retry typically arrives within seconds of the failed attempt. A nominal exponential backoff schedule (5 s doubling per attempt, capped at 5 minutes, ±15% jitter) is recorded on the delivery record's next_retry_at field, but is not enforced as a wait — do not rely on spacing between attempts |
| After the 5th failure | The delivery is marked PermanentlyFailed and never retried |
| Ordering | No ordering guarantee — deliveries are independent messages on a standard queue and can arrive out of order |
| Duplicates | At-least-once delivery — in rare cases the same delivery can arrive more than once; deduplicate on X-Webhook-Delivery-ID |
Delivery statuses
| Status | Description |
|---|---|
Pending | Queued for delivery |
InProgress | Currently being delivered |
Success | Endpoint returned 2xx |
Failed | Attempt failed, will retry |
PermanentlyFailed | All attempts exhausted |
Verifying authenticity
Every delivery is signed. Your signing secret (whsec_…) is generated when the webhook integration is created and is shown on the integration in the Liveday dashboard (and returned by the integrations API).
Each delivery carries two extra headers:
| Header | Value |
|---|---|
X-Webhook-Timestamp | Unix timestamp (seconds) when this attempt was signed |
X-Webhook-Signature | v1=<hex> — HMAC-SHA256 of {timestamp}.{raw_body} keyed with your signing secret |
To verify:
- Reject the request if
|now − X-Webhook-Timestamp|exceeds 300 seconds (replay protection). - Compute
expected = "v1=" + hex(hmac_sha256(secret, timestamp + "." + raw_body))over the exact raw request body bytes — verify before parsing, never re-serialize the JSON. - Compare with a constant-time comparison.
import hmac, hashlib, time
def verify_webhook(secret: str, headers: dict, raw_body: bytes, tolerance: int = 300) -> bool:
ts, sig = headers.get("X-Webhook-Timestamp", ""), headers.get("X-Webhook-Signature", "")
if not ts or not sig.startswith("v1="):
return False
if abs(time.time() - int(ts)) > tolerance:
return False # stale — possible replay
expected = "v1=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig) # constant-time
Retried deliveries are re-signed with a fresh timestamp. Integrations created before signing shipped receive their secret the next time the integration is updated; unsigned deliveries (no signature headers) can still be authenticated by looking up the X-Webhook-Delivery-ID via GET /webhooks/deliveries/{delivery_id}.
Monitoring deliveries
You can monitor webhook delivery status from the Integrations section of your Liveday dashboard, or via the API:
| Endpoint | Description |
|---|---|
GET /webhooks/deliveries | List all webhook deliveries for your organization |
GET /webhooks/deliveries/{delivery_id} | Fetch a single delivery (status, attempt count, retry schedule) |
GET /webhooks/deliveries/{delivery_id}/attempts | List each attempt: HTTP status code, response body (truncated to 5000 characters), duration, and error message |
These endpoints currently authenticate with a Liveday dashboard user JWT (not sk_ API keys).
Payload
The OrderCompleted payload is the full Order object as returned by the transactions API — see the Webhooks section of the API Reference for the complete, always up-to-date schema.
Best practices
- Verify the event type — check the
X-Webhook-Event-Typeheader before processing - Use the delivery ID for idempotency — the
X-Webhook-Delivery-IDheader uniquely identifies each delivery and is stable across its retries; use it to avoid processing duplicates - Return 2xx quickly — acknowledge receipt immediately and process asynchronously if needed
- Don't rely on ordering or retry spacing — retries can arrive within seconds and deliveries can arrive out of order
- Verify authenticity — deliveries are unsigned; confirm the delivery via the read API before acting on sensitive data