> ## Documentation Index
> Fetch the complete documentation index at: https://cadenya.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook events

> Every event Cadenya can POST to an agent's webhook URL, what triggers it, and how to verify and unwrap a delivery.

Set a webhook URL on an [agent](/docs/guides/the-basics/webhooks#configure-a-webhook) and Cadenya POSTs one delivery per event on every objective that agent runs. Every delivery uses the same envelope and the same signature scheme, so one handler covers all of them.

## Events

| Event                                                                                               | `data.objectiveEvent.data.type` | Fires when                                             |
| --------------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------ |
| [`objective_event.user_message`](/docs/api-reference/objective-user-message-event)                       | `userMessage`                   | An objective receives a message from your application. |
| [`objective_event.assistant_message`](/docs/api-reference/objective-assistant-message-event)             | `assistantMessage`              | The model finishes a turn.                             |
| [`objective_event.tool_called`](/docs/api-reference/objective-tool-called-event)                         | `toolCalled`                    | Cadenya dispatches a tool call.                        |
| [`objective_event.tool_result`](/docs/api-reference/objective-tool-result-event)                         | `toolResult`                    | A tool returns.                                        |
| [`objective_event.tool_approval_requested`](/docs/api-reference/objective-tool-approval-requested-event) | `toolApprovalRequested`         | A tool call needs a human decision.                    |
| [`objective_event.tool_approved`](/docs/api-reference/objective-tool-approved-event)                     | `toolApproved`                  | A reviewer approves a pending call.                    |
| [`objective_event.tool_denied`](/docs/api-reference/objective-tool-denied-event)                         | `toolDenied`                    | A reviewer denies a pending call.                      |
| [`objective_event.tool_error`](/docs/api-reference/objective-tool-error-event)                           | `toolError`                     | A tool call fails.                                     |
| [`objective_event.sub_agent_spawned`](/docs/api-reference/objective-sub-agent-spawned-event)             | `subAgentSpawned`               | An agent hands work to a sub-agent.                    |
| [`objective_event.sub_agent_updated`](/docs/api-reference/objective-sub-agent-updated-event)             | `subAgentUpdated`               | A sub-objective changes status.                        |
| [`objective_event.memory_read`](/docs/api-reference/objective-memory-read-event)                         | `memoryRead`                    | The agent loads a memory entry.                        |
| [`objective_event.reasoning`](/docs/api-reference/objective-reasoning-event)                             | `reasoning`                     | The model exposes its thinking for a turn.             |
| [`objective_event.finalized`](/docs/api-reference/objective-finalized-event)                             | `finalized`                     | The objective completes with its final result.         |
| [`objective_event.error`](/docs/api-reference/objective-error-event)                                     | `error`                         | The objective itself hits an error.                    |

## Envelope

Every objective event arrives in the same envelope. `type` names the event, `data` carries the agent, variation, and objective it belongs to, and `data.objectiveEvent.data` holds the fields documented on each event page.

<AccordionGroup>
  <Accordion title="Headers">
    Cadenya signs every delivery per the [Standard Webhooks](https://www.standardwebhooks.com/) specification and sends it as a `POST` with a JSON body.

    | Header              | Value                                                                                                                                                                                                                   |
    | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `webhook-id`        | Unique per delivery. Key your idempotency on it.                                                                                                                                                                        |
    | `webhook-timestamp` | Unix seconds when Cadenya sent the delivery. The SDKs reject anything more than five minutes off your clock.                                                                                                            |
    | `webhook-signature` | `v1,` followed by a base64 HMAC-SHA256 of `id.timestamp.body`, keyed with the base64-decoded part of your `whsec_` signing key. The header can carry several space-separated signatures. One valid signature is enough. |
    | `content-type`      | `application/json`                                                                                                                                                                                                      |

    Find the signing key under **Account Admin** and rotate it with [rotate the webhook signing key](/docs/api-reference/accountservice/rotates-the-webhook-signing-key-for-the-account). Cadenya records every attempt, and you can inspect them with [list webhook deliveries](/docs/api-reference/agentservice/list-webhook-deliveries).
  </Accordion>

  <Accordion title="Envelope fields">
    <ResponseField name="type" type="string" required>
      The event name, for example `objective_event.tool_called`.
    </ResponseField>

    <ResponseField name="timestamp" type="string" required>
      RFC 3339 time when Cadenya emitted the delivery.
    </ResponseField>

    <ResponseField name="data" type="object" required>
      Everything you need to route the event without a lookup.

      <Expandable title="properties">
        <ResponseField name="agent" type="object" required>
          Resource metadata of the agent: `id`, `name`, `workspaceId`, `accountId`, `externalId`, `labels`, `profileId`, `createdAt`, `updatedAt`.
        </ResponseField>

        <ResponseField name="agentVariation" type="object" required>
          Resource metadata of the variation that ran. Same shape as `agent`.
        </ResponseField>

        <ResponseField name="objective" type="object" required>
          Operation metadata of the objective: `id`, `workspaceId`, `accountId`, `externalId`, `labels`, `profileId`, `createdAt`. If you set `externalId` when you created the objective, it comes back here, so you can route on your own ID.
        </ResponseField>

        <ResponseField name="objectiveEvent" type="object" required>
          The event on the objective's timeline.

          <Expandable title="properties">
            <ResponseField name="metadata" type="object" required>
              Operation metadata of the event itself. `id` is the event ID (`objevt_…`), `createdAt` is when it was persisted.
            </ResponseField>

            <ResponseField name="data" type="object" required>
              The event-specific payload. `type` is the discriminator (`toolCalled`, `userMessage`, and so on) and the matching key holds the fields. See the **Event data** section of each event page.
            </ResponseField>

            <ResponseField name="contextWindowId" type="string" required>
              The context window the event belongs to. Changes when compaction opens a new window.
            </ResponseField>

            <ResponseField name="info" type="object">
              Extra context, when present: `objective` (operation metadata) and `createdBy` (the profile that caused the event).
            </ResponseField>

            <ResponseField name="startedAt" type="string">
              When the work this event records began. Present on events that measure something (an assistant turn, a tool execution), always together with `duration`.
            </ResponseField>

            <ResponseField name="duration" type="string">
              Elapsed time as a duration string, for example `"4.1s"`. Absent when the event is instantaneous.
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Accordion>
</AccordionGroup>

## Verify and unwrap

The SDKs read the signing key from `CADENYA_WEBHOOK_SECRET`, check the signature and timestamp, and return the typed envelope. Pass the raw request body, not a parsed and re-serialized one, because a single changed byte fails the check.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const event = await cadenya.webhooks.unwrap(rawBody, req.headers);
  ```

  ```go Go theme={null}
  event, err := client.Webhooks().Unwrap(rawBody, r.Header)
  ```

  ```ruby Ruby theme={null}
  event = client.unwrap_webhook(raw_body, headers)
  ```
</CodeGroup>

Each event page has a full handler you can run, plus a cURL script that signs and posts a sample delivery to it.

<Tip>
  Use the [Svix Playground](https://play.svix.com/) as a throwaway webhook URL while you shape your handler. It shows every delivery with its headers.
</Tip>
