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.

JSON
{
  "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"
  }
}
  • id names the event. Every webhook that hears the event gets the same id.

  • type is the name of the event, and data carries what the event is about: ids, amounts, statuses. It never carries a prompt or an answer of a model.

  • created_at is 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:

HTTP
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.

Terminal
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 2xx to 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, 410 or 422 ends the delivery: it is not sent again.

  • Any other answer — a redirect, 408, 429, a 5xx — 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.

ObjectEvents
reservation
  • reservation.created

    A hold was taken

  • reservation.captured

    A hold was captured

  • reservation.released

    A hold was released

  • reservation.expired

    A hold expired

  • reservation.overrun

    A capture came out above its hold

budget
  • budget.threshold_reached

    A budget crossed a threshold

  • budget.exceeded

    A budget reached its limit

operation
  • operation.authorized

    A gateway call was authorized

  • operation.denied

    A gateway call was refused

  • operation.started

    A gateway call went out

  • operation.completed

    A gateway call completed

  • operation.failed

    A gateway call failed

  • operation.uncertain

    A gateway call has no known outcome

flow
  • flow.published

    A flow version was published

  • flow.rolled_back

    A flow was rolled back

connection
  • connection.cooldown_started

    A provider connection cooled down

  • connection.health_changed

    A provider connection changed health

approval
  • approval.requested

    An execution waits for an approval

  • approval.decided

    An approval got its verdict

execution
  • execution.finished

    A flow execution reached its final status

alert
  • alert.opened

    An alert rule fired

  • alert.resolved

    An alert cleared

overrun
  • overrun.decided

    An overrun was decided

customer
  • customer.balance_low

    An end customer's credits ran low

ledger
  • ledger.degraded

    The ledger went degraded

  • ledger.restored

    The ledger is back

FamilyWhat 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 with reason set to background_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 until hold_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.requested announced.

  • 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