How do I receive approval decisions through webhooks?
Register a public HTTPS webhook in Settings → Notifications and select approval events. Orlo sends a signed notification when the chosen lifecycle event occurs. Verify and save it, then read the current approval status before resuming a saved agent step.
Choose how to receive updates
| What your application needs | Use |
|---|---|
| The decision before a tool runs | The response from the governed-step request |
| Progress while a run is open | The session event stream |
| A notification when a person decides, including while your worker is offline | An approval webhook |
| Current status after a notification, reconnect, or restart | GET /v1/agent-approvals/:id |
Webhooks cover the approval lifecycle. They are not sent for every logged step or every immediate allow or reject decision. Those decisions are in the API response and the session’s recorded events.
1. Register a receiver
An admin opens Settings → Notifications → Add a webhook, enters a public HTTPS receiver URL, and selects events. For an application waiting for a final answer, select Approved, Rejected, and Lapsed. Select Waiting for a decision as well if the receiver also routes review requests.
Store the signing secret in your receiver’s secret store. Orlo shows it once. Use Edit events to change an existing subscription without changing its address or secret. Existing subscriptions keep their selected events until you edit them.
The notification endpoint API supports the same setup.
| Event | Meaning |
|---|---|
approval.requested |
A step is waiting for a person |
approval.expiring |
A pending approval is near its deadline |
approval.escalated |
The first deadline passed and the rule gives admins another review window; it is still pending |
approval.approved |
A person approved the request |
approval.rejected |
A person rejected the request |
approval.expired |
The request closed without a person’s decision; the configured timeout action applies |
Reviewer email handles requests and deadline notices when email is configured. Final approval and rejection events are for webhook receivers.
2. Verify each delivery
Orlo sends a JSON POST with these fields:
| Field | Purpose |
|---|---|
id |
Stable event ID; use it to deduplicate deliveries |
type |
One of the events above |
org_id |
Organization that owns the event |
occurred_at |
When the event was recorded |
approval |
Approval snapshot, including approval_id, session_id, step_id, task_id, status, and deadline fields; human decisions also carry decided_at and decided_by |
The headers include X-Orlo-Event-Id, X-Orlo-Event-Type, and X-Orlo-Signature. The signature has the form t=<unix seconds>,v1=<hex digest>.
Verify the exact raw request body before JSON parsing. The digest is HMAC-SHA256 over the timestamp, a period, and the raw body, using the signing secret exactly as returned. This Node.js example also rejects timestamps more than five minutes away from the receiver’s clock:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyOrloSignature(rawBody, signature, secret, now = Date.now()) {
if (!secret || typeof signature !== 'string') return false;
const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signature);
if (!match) return false;
const timestamp = Number(match[1]);
if (!Number.isSafeInteger(timestamp) || Math.abs(now / 1000 - timestamp) > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${match[1]}.`).update(rawBody).digest();
const received = Buffer.from(match[2], 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
}
Use your HTTP framework’s raw-body support, with an appropriate request-size limit. Keep the receiver’s clock synchronized. Reject invalid signatures. After verification, validate the event type and organization, and use the signed body’s id as the deduplication key. Do not log the secret or full request payloads.
3. Accept quickly and process once
Save the verified event and queue your worker durably, then return 2xx within five seconds. Deduplicate by the signed event ID. Do not wait for tool execution before acknowledging.
Failed delivery, retries, and replay explains the destination-specific retry schedule, back pressure, retention, and manual replay. Reconcile outstanding approvals by ID because delivery can be delayed or duplicated.
4. Read the decision and resume the saved step
Resume the approved action using the current approval status and the original session, step ID, tool, action class, and arguments. Preserve that state before waiting. A notification does not authorize bypassing the governed-step check.
Watch a running session
GET /v1/agent-sessions/:id/events/stream uses server-sent events. It includes step:governed, tool outcomes, and approval:decided. Reconnect with the last event ID using Last-Event-ID or the after query parameter. Read the approval by ID when resuming a durable job so that a missed or delayed event does not determine what runs.
See Agent API, TypeScript SDK, and Python and LangGraph for the governed call path.