Receive the events of Slen
What a delivery of a webhook carries, how to check its signature, when it is sent again, and what each family of events reports.
6 min read
A webhook sends the events of Slen to an address of your service as they happen: a hold taken, a budget nearly spent, a call that ended, an execution that waits for a decision. This page is about what arrives at that address and how to read it. The webhooks are added and changed in the console — Webhooks shows how.
What a delivery carries
A delivery is a POST with a JSON body and the header Content-Type: application/json. The body is the event.
{
"id": "74443412671954944",
"type": "operation.uncertain",
"created_at": "2026-10-02T09:13:04+00:00",
"data": {
"reservation_id": "74442097331195904",
"project_id": "74438200973594624",
"operation_code": "generate_email",
"external_user_id": "user_8421",
"model": "gpt-4o-mini",
"reason": "operation_uncertain",
"hold_expires_at": "2026-10-02T09:27:04+00:00"
}
}idnames the event. Every webhook that hears the event gets the sameid.typeis the name of the event, anddatacarries what the event is about: ids, amounts, statuses. It never carries a prompt or an answer of a model.created_atis the moment the event happened, not the moment of the delivery.
Check the signature
Every delivery is signed with the secret of the webhook, shown once when the webhook is created. The signature travels in a header of the delivery, beside the type of its body:
POST /webhooks/slen HTTP/1.1
Content-Type: application/json
Slen-Signature: t=1759396384,v1=5f0c8e2a…t is the time of signing in seconds since 1970, and v1 is the signature.
1. Rebuild the signed text
Join the value of t, a full stop and the body exactly as it arrived, byte for byte. Do not parse the body and write it again first.
2. Compute the signature
Compute the HMAC-SHA256 of that text with the secret as the key, written as lowercase hex.
printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET"3. Compare
Compare the result with the value of v1 in constant time. Refuse a delivery whose signature differs, and one whose t is far from your clock — that is an old delivery sent again by somebody else.
When a delivery is sent again
Your service answers with a
2xxto take a delivery. Answer at once and do the work afterwards: Slen waits 10 seconds for the answer, unless the service is set up otherwise.An answer
400,401,403,404,410or422ends the delivery: it is not sent again.Any other answer — a redirect,
408,429, a5xx— or no answer in time is a failure, and the delivery is sent again later. The pause starts at 30 seconds and doubles each time, up to an hour. After 12 attempts the delivery is given up. These are the defaults of the service.Deliveries go out once a minute.
The same event may arrive more than once, and two events may arrive out of their order. Keep the id of each event you have handled, and skip one you have already seen.
Which webhooks hear an event
A webhook hears the events it is subscribed to.
A webhook of one project hears the events of that project.
A webhook of all projects hears the events of every project of the organization, and the events about no project: every
connection.cooldown_started, and the events of a connection, a budget or an alert of the whole organization.A webhook that is switched off hears nothing. A delivery already waiting for it is still sent.
The events
These are the events Slen sends. The form of the console offers some of them; any of them can be named through the API.
| Object | Events |
|---|---|
reservation |
|
budget |
|
operation |
|
flow |
|
connection |
|
approval |
|
execution |
|
alert |
|
overrun |
|
customer |
|
ledger |
|
| Family | What its events report |
|---|---|
reservation. | A hold was taken, captured, released or expired; reservation.overrun — a capture came out above the hold |
operation. | A model call of a flow: the hold it took (operation.authorized), a refusal by a budget, a policy or the block of a customer before the call (operation.denied), the start of the call at the provider, and its end — operation.completed, operation.failed or operation.uncertain |
budget. | A hold brought the use of a budget to 50 % or 80 % of its limit (budget.threshold_reached), or to the whole limit (budget.exceeded) |
flow. | A version of a flow was published, or the flow was rolled back to an earlier version |
execution. | An execution ended, with its status and error_code (execution.finished) |
approval. | An execution waits for a decision (approval.requested), and the decision: approved, rejected or expired (approval.decided) |
overrun. | A model node found that a call would cost more than its hold, and its overrun policy decided what to do (overrun.decided) |
connection. | A provider connection was taken out of use for a while (connection.cooldown_started), or its status changed (connection.health_changed) |
alert. | An alert opened, or was resolved |
customer. | The credits a customer has available fell below the level set for their account (customer.balance_low) |
ledger. | Slen began to let calls through without its ledger (ledger.degraded), and the end of that time (ledger.restored) |
A budget announces each level at most once a window — Budgets says when. An alert announces its opening and its resolution, not each refresh — Alerts.
The events of a model call
The events of the families reservation. and operation., and overrun.decided, come from the model calls of flows. The end of a model call is one of three events, and each says something different about the money.
operation.completed— the provider answered.operation.failed— the call did not happen, and its hold was given back. One exception: a call the provider ran in the background whose hold was settled by another capture while it waited ends withreasonset tobackground_hold_expired. Nothing is given back, and the provider may still bill the call.operation.uncertain— nobody knows how the call ended: the answer of a model node was lost, the provider did not answer the start of a background call, or a background call could no longer be asked about. The hold stays untilhold_expires_at, because the provider may have been paid. Do not repeat such a call as if it had failed — When the outcome is unknown says what to do.
A call the provider runs in the background announces its start like any call, and its end when it comes, with one of the three events.
Where to go next
Webhooks — add a webhook, choose its events and keep its secret.
When the outcome is unknown — what to do with a call that ended
uncertain.Approvals — decide on an execution that
approval.requestedannounced.Operations — the record of a model call that the events of the family
operation.are about.Webhooks — check the signature of a delivery and read its event in the SDK.
Built from openapi b070e1f, sdk contract 6a2e0e0, php 4089db3, node 0fa87be