Skip to main content
When a tool needs a human yes, Cadenya pauses the objective and POSTs a webhook to your server. In this lesson you build a handler that verifies the webhook, reads the objective’s labels to find who to ask, then uses the Cadenya SDK to approve the call or deny it with a steering memo. Examples come in TypeScript and Go.

What you need

  • An agent with a tool that requires approval, and its webhook endpoint pointed at your server. Set the agent’s Events URL under Webhook configuration on its form (the webhookEventsUrl field on the agent spec). The Wrap an HTTP API lesson builds an approval-gated tool, and Run your first objective shows the approval pause.
  • Your webhook signing secret in CADENYA_WEBHOOK_KEY, and your API key in CADENYA_API_KEY.
  • The SDK for your language, and a public URL to your server (ngrok or a deployed endpoint).
Agent Webhook configuration with an ngrok Events URL ending in webhooks/cadenya

The agent sends every objective event to this endpoint

Step 1: Put routing data in labels

The webhook carries the objective’s labels and external ID, not its custom data. So stash whatever you need to route a notification into labels when you start the objective. Set the external ID to your own key, such as the support ticket.
Labels and the external ID ride in every webhook for the objective. The custom data object does not. Mirror the routing keys you need into labels, and fetch the objective by ID later if you need the rest.

Step 2: Receive and verify the webhook

Your agent’s webhook URL gets a POST for every event. Read the raw body and headers, then hand them to unwrap. It checks the Standard Webhooks signature against your CADENYA_WEBHOOK_KEY and parses the payload. A bad signature throws, so a failed verify never reaches your logic.
Testing against synthetic payloads with no tunnel yet? Use unsafeUnwrap, which parses without checking the signature. Switch back to unwrap before you ship.

Step 3: Find who to ask

Filter to the approval event, then read the routing keys you stored in Step 1. The tool call ID lives on the event payload, and the objective ID is the handle you approve against.
The fields you can read off the payload:
The event name, such as objective_event.tool_approval_requested. Branch on it to handle each kind of event.
The objective’s operation metadata: id, externalId, labels, plus account and workspace IDs. This is your routing source.
The event itself. For an approval, its data.toolApprovalRequested.toolCallId is the call you decide on.
Resource metadata for the agent and the variation that produced the event, when you want to log or branch on which agent asked.

Step 4: Approve, or deny with steering

This call runs from your reviewer-facing action, a Slack button handler or an approval link, not the webhook handler. Persist the workspaceId, objectiveId, and toolCallId from Step 3 with the notification (a button value, a signed link) so the action has them when the click arrives. Then send the decision with the SDK. Approve resumes the call as written. Deny hands the agent a memo, which steers it toward a different choice instead of stopping it cold.
Approve and deny take the workspace ID, the objective ID, and the tool call ID, all of which ride on the webhook. The objective ID also accepts the external_id: form, so your ticket number works as the handle too.
The objective leaves its paused state. On approve, the agent calls the tool and reads the result. On deny, the agent gets the rejected call back as its result, a denial carrying your memo, so it reads your steer for that exact call and picks another path.
Objective timeline showing the approval request, approval decision, reviewByUrl execution, tool result, and final assistant message

An approved call resumes from the same timeline

A pending approval waits up to 24 hours. If no one approves or denies it in that window, the call times out and fails (it is not auto-approved or auto-denied), and the objective surfaces an error. Notify your reviewer promptly, and handle the timeout the same way you handle any failed run.

What you built

You stood up a handler that verifies a Cadenya webhook and routes the approval to a reviewer using labels you set at create time, plus a decision action that answers with the SDK. Swap notifyReviewer for a Slack post or an email and you have a working approval loop.

Going further

How objectives work

Every event type that can reach your handler, and the objective lifecycle behind them.

Run your first objective

Where approvals fit in the full objective arc: data, secrets, and structured output.

Email updates from an objective

The same handler, reacting to finish and failure events instead of approvals.