Reference
Webhooks
What LevelFour posts to your endpoint when a recommendation or an optimization changes state, and how to verify the signature before you act on it.
LevelFour delivers webhooks through Svix. When events occur in your account, LevelFour sends HTTPS POST requests to your registered endpoints with a signed payload.
The snippets below use the LevelFour SDK clients. SDKs covers installing one and building client.
Register an endpoint
endpoint = client.webhooks.register(
url="https://your-app.com/webhooks/levelfour",
event_types=["recommendation.accepted", "optimization.completed"],
description="Production webhook",
)The call takes:
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint URL |
event_types | string[] | Yes | Events to subscribe to |
description | string | No | Human-readable description |
url must be HTTPS. Webhooks in the API reference has the full request and response schema.
Manage endpoints
List endpoints
endpoints = client.webhooks.list()Delete an endpoint
client.webhooks.delete("ep_123")Events
| Event | Description |
|---|---|
recommendation.terms_accepted | Someone accepted the terms for applying a recommendation |
recommendation.accepted | A savings recommendation was accepted |
recommendation.rejected | A savings recommendation was rejected |
recommendation.rejection_reason_added | A reason was added to a rejected recommendation |
recommendation.implementation_method_set | Someone chose how a recommendation is delivered |
execution.approval_requested | A member submitted a recommendation for an admin to release |
optimization.started | An automated optimization has started processing |
optimization.completed | An automated optimization finished successfully |
optimization.failed | An automated optimization failed |
Payloads
recommendation.accepted / recommendation.rejected
{
"recommendation_id": "REC-1234",
"saving_acceptance": "accepted",
"saving_accepted_by": "user@example.com",
"saving_accepted_at": "2025-10-15T10:00:00",
"rejection_reason": null,
"rejection_explanation": null,
"status": "optimized"
}A rejected event carries rejection_reason and rejection_explanation, and its status is rejected.
recommendation.terms_accepted
{
"recommendation_id": "REC-1234",
"terms_accepted_by": "user@example.com",
"terms_accepted_at": "2025-10-15T10:00:00Z"
}recommendation.rejection_reason_added
{
"recommendation_id": "REC-1234",
"saving_acceptance": "rejected",
"rejection_reason": "not_applicable",
"rejection_explanation": "This volume backs a disaster recovery test."
}rejection_reason is one of operational, strategy, not_applicable or other.
recommendation.implementation_method_set
{
"recommendation_id": "REC-1234",
"implementation_method": "iac",
"status": "pending"
}execution.approval_requested
{
"recommendation_id": "REC-1234",
"execution_request_status": "pending_approval",
"execution_request_by": "user@example.com"
}Subscribe an endpoint your approvers watch to this event. Without it, a request waits in the dashboard until an admin opens it.
optimization.started
{
"recommendation_id": "REC-1234",
"status": "processing",
"implementation_method": "one-click",
"completed_at": "2025-10-15T10:05:00Z",
"message": "Optimization started"
}The implementation_method field can be: one-click, iac, one-click-plus-iac, or manual. l4 rec execute has a table of what each one does.
optimization.completed
{
"recommendation_id": "REC-1234",
"status": "optimized",
"implementation_method": "one-click"
}optimization.failed
{
"recommendation_id": "REC-1234",
"status": "failed",
"implementation_method": "one-click"
}Verify the signature
Every webhook request carries these headers for HMAC-SHA256 signature verification:
| Header | Description |
|---|---|
webhook-id | Unique message ID (for deduplication) |
webhook-timestamp | Unix timestamp in seconds |
webhook-signature | v1,<base64-encoded-signature> |
svix-id, svix-timestamp, svix-signature) are also supported.from levelfour.webhooks.verifier import WebhookVerifier, WebhookVerificationError
verifier = WebhookVerifier("whsec_your_signing_secret")
try:
payload = verifier.verify(
payload=request_body,
headers={
"webhook-id": headers["webhook-id"],
"webhook-timestamp": headers["webhook-timestamp"],
"webhook-signature": headers["webhook-signature"],
},
)
handle_event(payload)
except WebhookVerificationError:
return Response(status_code=400)The algorithm
Each signature is an HMAC over the message id, the timestamp and the body, joined by dots:
base64(HMAC-SHA256(base64_decode(secret), "{webhook-id}.{webhook-timestamp}.{body}"))whsec_. The SDK verifiers strip this prefix before they base64-decode the secret bytes, so pass the value exactly as it was issued.Timestamp tolerance
By default, the verifiers reject messages with timestamps more than 5 minutes from the current time. This prevents replay attacks. You can customize the tolerance:
payload = verifier.verify(body, headers, tolerance_seconds=600)A wider tolerance widens the window in which a captured request can be replayed.
Retries
Svix retries failed deliveries with exponential backoff. A delivery fails if your endpoint:
- Returns a non-2xx HTTP status code
- Does not respond within the timeout
- Is unreachable
2xx is what tells LevelFour the delivery worked. Return it as soon as the signature checks out and the event is stored, then do the heavy processing asynchronously.webhook-id can reach you more than once. Implement idempotent handlers using the webhook-id header for deduplication.Next
- SDKs has the Python, TypeScript and Go clients and their constructor options
- Webhooks in the API reference has the endpoint schemas and status codes
- Slack integration builds a receiver that verifies signatures and posts to a channel, in Python and TypeScript
- Google Chat integration does the same for a Google Chat space
Google Cloud
Reads only Google Cloud answers: which data has arrived, resources by type, region and project, idle resources, label coverage, utilization, budgets, and what LevelFour's service account can read.
Error Handling
Which HTTP status raises which class in each SDK, what an error carries, and which failures the SDKs retry before you see them.