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

# Reasoning

> Cadenya sends objective_event.reasoning with the model's thinking text for a turn, when the model exposes it.

## When it fires

* The model produces extended thinking or a reasoning summary alongside a response. It arrives next to the [assistant message](/docs/api-reference/objective-assistant-message-event) from the same model response.
* The text is informational only. Cadenya never sends it back to the model, and models that do not expose reasoning never emit it.

<RequestExample>
  ```json Payload theme={null}
  {
    "type": "objective_event.reasoning",
    "timestamp": "2026-08-26T14:03:11Z",
    "data": {
      "agent": {
        "id": "agent_01HXKD2E5NQM3T9AYWCFMGWT9Y",
        "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
        "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
        "name": "Support Triage",
        "externalId": "",
        "labels": {},
        "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
        "createdAt": "2026-08-01T09:00:00Z",
        "updatedAt": "2026-08-01T09:00:00Z"
      },
      "agentVariation": {
        "id": "agentvar_01HXKD2E5NQM3T9AYWCF32BSPP",
        "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
        "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
        "name": "sonnet-strict-tools",
        "externalId": "",
        "labels": {},
        "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
        "createdAt": "2026-08-01T09:00:00Z",
        "updatedAt": "2026-08-01T09:00:00Z"
      },
      "objective": {
        "id": "obj_01HXKD2E5NQM3T9AYWCFQAZGFV",
        "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
        "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
        "externalId": "ticket-4821",
        "labels": {
          "source": "zendesk"
        },
        "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
        "createdAt": "2026-08-26T14:03:11Z"
      },
      "objectiveEvent": {
        "metadata": {
          "id": "objevt_01HXKD2E5NQM3T9AYWCF8ZWBY0",
          "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
          "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
          "externalId": "",
          "labels": {},
          "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
          "createdAt": "2026-08-26T14:03:11Z"
        },
        "contextWindowId": "objwin_01HXKD2E5NQM3T9AYWCFN7BSTR",
        "data": {
          "type": "reasoning",
          "reasoning": {
            "content": "The subtotal is 118.00 and tax is 10.40, so the 128.40 total is correct. The customer likely compared it against the pre-tax quote."
          }
        }
      }
    }
  }
  ```

  ```http Headers theme={null}
  POST /webhooks/cadenya HTTP/1.1
  Host: example.com
  Content-Type: application/json
  webhook-id: wh_01HXKD2E5NQM3T9AYWCFGVF6Y6
  webhook-timestamp: 1787752991
  webhook-signature: v1,K5oZfzN95Z9UVu1EsPQhBaJMSUuGgeEtBXQrZ2lZ+1s=
  ```
</RequestExample>

## Event data

The event-specific fields live at `data.objectiveEvent.data`. The rest of the payload is the shared [envelope](#envelope).

<ResponseField name="type" type="string" required>
  Always `reasoning`. Switch on this, not on the outer `type`, when one handler serves several events.
</ResponseField>

<ResponseField name="reasoning" type="object" required>
  The thinking.

  <Expandable title="properties">
    <ResponseField name="content" type="string" required>
      The reasoning text. A verbatim chain of thought or a provider-generated summary, depending on the model.
    </ResponseField>
  </Expandable>
</ResponseField>

## Handle it

Every SDK verifies the signature and returns the typed envelope in one call. The handler reads the raw body (not a parsed and re-serialized one), unwraps it, and returns a 2xx fast.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createServer } from "node:http";
  import Cadenya from "@cadenya/cadenya";

  // Reads CADENYA_API_KEY and CADENYA_WEBHOOK_SECRET from the environment.
  const cadenya = new Cadenya();

  createServer(async (req, res) => {
    if (req.method !== "POST" || req.url !== "/webhooks/cadenya") {
      res.writeHead(404).end();
      return;
    }

    const chunks = [];
    for await (const chunk of req) chunks.push(chunk);
    const payload = Buffer.concat(chunks).toString("utf8");

    let event;
    try {
      // Checks the signature and timestamp, then parses the envelope.
      event = await cadenya.webhooks.unwrap(payload, req.headers);
    } catch {
      res.writeHead(401).end("bad signature");
      return;
    }

    if (event.type === "objective_event.reasoning") {
      const { objective, objectiveEvent } = event.data;
      if (objectiveEvent.data.type === "reasoning") {
        console.log(objective.id, "thought:", objectiveEvent.data.reasoning.content);
      }
    }

    // Answer quickly. Do slow work after the 2xx.
    res.writeHead(200).end();
  }).listen(3000);
  ```

  ```go Go theme={null}
  package main

  import (
  	"io"
  	"log"
  	"net/http"

  	cadenya "go.cadenya.com/cadenya-go"
  )

  func main() {
  	// Reads CADENYA_API_KEY and CADENYA_WEBHOOK_SECRET from the environment.
  	client, err := cadenya.NewClient()
  	if err != nil {
  		log.Fatal(err)
  	}

  	http.HandleFunc("POST /webhooks/cadenya", func(w http.ResponseWriter, r *http.Request) {
  		payload, err := io.ReadAll(r.Body)
  		if err != nil {
  			http.Error(w, "read failed", http.StatusBadRequest)
  			return
  		}

  		// Checks the signature and timestamp, then parses the envelope.
  		event, err := client.Webhooks().Unwrap(payload, r.Header)
  		if err != nil {
  			http.Error(w, "bad signature", http.StatusUnauthorized)
  			return
  		}

  		if r := event.Data.ObjectiveEvent.Data.Reasoning; r != nil {
  			log.Printf("%s thought: %s", event.Data.Objective.ID, r.Reasoning.Content)
  		}

  		// Answer quickly. Do slow work after the 2xx.
  		w.WriteHeader(http.StatusOK)
  	})

  	log.Fatal(http.ListenAndServe(":3000", nil))
  }
  ```

  ```ruby Ruby theme={null}
  # gem install sinatra cadenya
  require "sinatra"
  require "cadenya"

  # Reads CADENYA_API_KEY and CADENYA_WEBHOOK_SECRET from the environment.
  client = Cadenya::Client.new

  post "/webhooks/cadenya" do
    payload = request.body.read
    headers = {
      "webhook-id" => request.env["HTTP_WEBHOOK_ID"],
      "webhook-timestamp" => request.env["HTTP_WEBHOOK_TIMESTAMP"],
      "webhook-signature" => request.env["HTTP_WEBHOOK_SIGNATURE"]
    }

    begin
      # Checks the signature and timestamp, then parses the envelope.
      event = client.unwrap_webhook(payload, headers)
    rescue Cadenya::WebhookVerificationError
      halt 401, "bad signature"
    end

    if event.type == "objective_event.reasoning"
      data = event.data.objective_event.data
      if data.is_a?(Cadenya::Types::ObjectiveEventData_Reasoning)
        puts "#{event.data.objective.id} thought: #{data.reasoning.content}"
      end
    end

    # Answer quickly. Do slow work after the 2xx.
    status 200
  end
  ```

  ```bash cURL theme={null}
  # Point SECRET at the same whsec_ key your handler uses, or verification fails.
  SECRET="whsec_V2ViaG9va3NBcmVGdW5Ub0RvY3VtZW50ISEhISEhISE="
  URL="http://localhost:3000/webhooks/cadenya"

  ID="wh_01HXKD2E5NQM3T9AYWCFGVF6Y6"
  TS="$(date +%s)"
  BODY="$(cat <<'JSON'
  {
    "type": "objective_event.reasoning",
    "timestamp": "2026-08-26T14:03:11Z",
    "data": {
      "agent": {
        "id": "agent_01HXKD2E5NQM3T9AYWCFMGWT9Y",
        "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
        "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
        "name": "Support Triage",
        "externalId": "",
        "labels": {},
        "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
        "createdAt": "2026-08-01T09:00:00Z",
        "updatedAt": "2026-08-01T09:00:00Z"
      },
      "agentVariation": {
        "id": "agentvar_01HXKD2E5NQM3T9AYWCF32BSPP",
        "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
        "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
        "name": "sonnet-strict-tools",
        "externalId": "",
        "labels": {},
        "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
        "createdAt": "2026-08-01T09:00:00Z",
        "updatedAt": "2026-08-01T09:00:00Z"
      },
      "objective": {
        "id": "obj_01HXKD2E5NQM3T9AYWCFQAZGFV",
        "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
        "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
        "externalId": "ticket-4821",
        "labels": {
          "source": "zendesk"
        },
        "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
        "createdAt": "2026-08-26T14:03:11Z"
      },
      "objectiveEvent": {
        "metadata": {
          "id": "objevt_01HXKD2E5NQM3T9AYWCF8ZWBY0",
          "accountId": "account_01HXKD2E5NQM3T9AYWCFTJHJVF",
          "workspaceId": "workspace_01HXKD2E5NQM3T9AYWCF133E3Q",
          "externalId": "",
          "labels": {},
          "profileId": "profile_01HXKD2E5NQM3T9AYWCFS0AP08",
          "createdAt": "2026-08-26T14:03:11Z"
        },
        "contextWindowId": "objwin_01HXKD2E5NQM3T9AYWCFN7BSTR",
        "data": {
          "type": "reasoning",
          "reasoning": {
            "content": "The subtotal is 118.00 and tax is 10.40, so the 128.40 total is correct. The customer likely compared it against the pre-tax quote."
          }
        }
      }
    }
  }
  JSON
  )"

  # Standard Webhooks: HMAC-SHA256 over "id.timestamp.body", keyed with the base64-decoded secret.
  KEY_HEX="$(printf '%s' "${SECRET#whsec_}" | base64 -d | xxd -p -c 256)"
  SIG="$(printf '%s.%s.%s' "$ID" "$TS" "$BODY" \
    | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY_HEX" -binary \
    | base64)"

  curl -X POST "$URL" \
    -H "content-type: application/json" \
    -H "webhook-id: $ID" \
    -H "webhook-timestamp: $TS" \
    -H "webhook-signature: v1,$SIG" \
    --data-binary "$BODY"
  ```
</CodeGroup>

The cURL tab signs the sample payload the same way Cadenya does and posts it to a local handler. Run one of the other tabs first, set `SECRET` to the key that handler uses, then run the script to see the event land.

## 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>

## Related

* [Assistant message event](/docs/api-reference/objective-assistant-message-event)
* [List objective events](/docs/api-reference/objectiveservice/list-objective-events)
