Skip to main content

Webhook Triggers

Webhooks allow external systems (n8n, Zapier, Twilio, custom apps) to activate agents via HTTP POST.

Authentication

  • Read endpoints (GET): JWT + x-tenant-id or API Key
  • Write endpoints (POST/PATCH/DELETE): JWT required
  • Execution endpoints: Own validation (HMAC, API Key, or none)

CRUD Endpoints

These endpoints apply to all trigger types (webhook, email, db_event, schedule).

POST /api/triggers

Create a new trigger.

Request Body:

{
"agent_id": "uuid",
"name": "ERP Chat Integration",
"trigger_type": "webhook",
"trigger_config": {
"auth": { "type": "api_key" },
"query_extraction": { "mode": "field", "field": "chatInput" },
"context_mapping": {
"userId": "$.userId",
"userName": "$.userName"
},
"response_adapter": { "format": "raw", "split_messages": true, "max_messages": 10 },
"session_strategy": { "mode": "derive", "fields": ["userId", "companyId"] }
}
}

Response (201):

{
"success": true,
"data": {
"id": "uuid",
"agent_id": "uuid",
"name": "ERP Chat Integration",
"trigger_type": "webhook",
"webhook_secret": "whs_example_abc123...",
"enabled": true
},
"webhook_url": "https://llm.zihin.ai/api/triggers/webhook/uuid"
}
caution

The webhook_secret is only shown at creation time. Store it securely for HMAC validation.

Other CRUD

EndpointMethodAuthDescription
GET /api/triggersGETHybridList triggers
GET /api/triggers/:idGETHybridGet trigger details
PATCH /api/triggers/:idPATCHJWTUpdate trigger
DELETE /api/triggers/:idDELETEJWTDelete trigger
POST /api/triggers/:id/enablePOSTJWTEnable trigger
POST /api/triggers/:id/disablePOSTJWTDisable trigger
GET /api/triggers/:id/executionsGETHybridExecution history
GET /api/triggers/:id/executions/:executionIdGETHybridEnriched execution details
POST /api/triggers/:id/testPOSTJWTDry-run test
GET /api/triggers/statsGETHybridAggregated statistics
GET /api/triggers/typesGETHybridAvailable trigger types

Webhook Execution

POST /api/triggers/webhook/:id

Execute a webhook trigger. Uses validation configured in trigger_config.auth.

Auth by type:

auth.typeHeader RequiredDescription
signatureX-Webhook-Signature: sha256=<hmac>HMAC-SHA256/SHA512 with webhook_secret
api_keyX-Api-Key: YOUR_API_KEYTenant API Key
noneNo validation

Sync Response (default):

{
"success": true,
"trigger_id": "uuid",
"agent_id": "uuid",
"agent_response": "Found 5 active contracts...",
"execution_id": "uuid",
"usage": { "total_tokens": 1250 },
"latency_ms": 15234
}

ERP Format Response (when response_adapter.format = "ebarn"):

[
{ "message": "Found 5 active contracts:", "type": "text" },
{ "message": "1. Contract ABC - $50,000\n2. Contract DEF - $30,000", "type": "text" }
]

Async Execution with Callback

Triggers can execute in async mode: the webhook responds immediately (202 Accepted) and delivers the agent response via HTTP callback.

Configuration

Add execution inside trigger_config:

{
"execution": {
"mode": "async",
"timeout_ms": 120000,
"ack_response": {
"body": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response></Response>",
"content_type": "text/xml"
},
"callback": {
"url": "https://api.twilio.com/2010-04-01/Accounts/{{accountSid}}/Messages.json",
"method": "POST",
"content_type": "application/x-www-form-urlencoded",
"auth": { "type": "basic", "secret_ref": "TWILIO_AUTH" },
"body_template": {
"To": "{{phoneNumber}}",
"From": "whatsapp:+14155238886",
"Body": "{{agent_response}}"
},
"response_transform": {
"strip_markdown": true,
"max_length": 1550
}
}
}
}

Execution Fields

FieldTypeDefaultDescription
modestringsyncsync or async
timeout_msnumber30000Agent execution timeout (1s-10min)
ack_response.bodyanyImmediate response body
callback.urlstringCallback URL (supports {{placeholders}})
callback.methodstringPOSTPOST, PUT, or PATCH
callback.auth.typestringnonebasic, bearer, api_key, or none
callback.auth.secret_refstringVault secret name
callback.body_templateobjectTemplate with {{agent_response}}, etc.
callback.response_transformobjectTransform rules (strip_markdown, max_length)

Auto-derived Transform

When response_transform is omitted, it is derived from response_adapter.format:

Formatstrip_markdownmax_length
ebarnfalse
twimltrue4096
rawfalse

Response Splitting

Long agent responses can be automatically split into multiple chunks for channels with message size limits (WhatsApp ~1600 chars, SMS 160 chars, etc.).

Split is domain-agnostic — it works with any trigger type and any callback endpoint. The agent generates its best response; the infrastructure adapts to channel constraints.

Configuration

Add split_config inside execution:

{
"execution": {
"mode": "async",
"callback": { "..." : "..." },
"split_config": {
"enabled": true,
"max_chunk_size": 1500,
"max_chunks": 10,
"chunk_strategy": "paragraph",
"numbering": true,
"inter_chunk_delay_ms": 500
}
}
}

Split Config Fields

FieldTypeDefaultDescription
enabledbooleanfalseEnable response splitting
max_chunk_sizenumber1500Maximum characters per chunk (100-50000)
max_chunksnumber10Maximum number of chunks (1-50)
chunk_strategystringparagraphSplit strategy: paragraph, sentence, or length
numberingbooleantrueAdd (1/N) header to each chunk
inter_chunk_delay_msnumber500Delay between chunk deliveries in ms (0-10000)

Chunk Strategies

StrategyDescriptionBest For
paragraphSplits on \n\n boundaries, regroups small paragraphsGeneral text, reports
sentenceSplits on sentence endings (.!?), regroups short sentencesConversational text
lengthHard split at exact character countRaw data, code blocks

Behavior

  • If response fits in a single chunk, no splitting occurs
  • Each chunk is delivered via the configured callback sequentially
  • If a chunk delivery fails, remaining chunks are not sent (fail-fast)
  • Numbering format: (1/3)\nFirst chunk text...
Sync Mode

Split works best with mode: "async". In sync mode, the response is returned directly — split only affects adapters that support arrays (e.g., ERP format). For most channels, use async mode with callback.

Channelmax_chunk_sizeinter_chunk_delay_msNotes
WhatsApp (Twilio)1500500Sandbox truncates at ~1600
SMS140300160 char limit with encoding overhead
Slack4000200Block Kit has 3000 char limit per block
ERP30000Array format, no delay needed

Message Buffering (Popcorn)

When users send multiple short messages in rapid succession ("popcorn messages"), each message would normally trigger a separate agent execution. Message buffering consolidates these into a single execution.

How It Works

User sends:    "Olá"          →  buffered (timer starts: 5s)
User sends: "preciso de" → buffered (timer resets: 5s)
User sends: "ajuda" → buffered (timer resets: 5s)
... 5 seconds pass ...
Agent receives: "Olá\npreciso de\najuda" → single execution

The buffer uses a sliding window: each new message resets the timer. After the window expires without new messages, all accumulated messages are concatenated and sent as a single agent execution.

Configuration

Add message_buffer inside execution:

{
"execution": {
"mode": "async",
"callback": { "..." : "..." },
"message_buffer": {
"enabled": true,
"window_ms": 5000,
"max_messages": 10,
"concat_separator": "\n"
}
}
}

Message Buffer Fields

FieldTypeDefaultDescription
enabledbooleanfalseEnable message buffering
window_msnumber5000Debounce window in milliseconds (100-60000)
max_messagesnumber10Maximum messages in buffer before forced execution (1-100)
concat_separatorstring\nSeparator used when joining buffered messages

Behavior

  • Requires Redis — if Redis is unavailable, messages execute immediately (fail-safe)
  • Buffer is per-session (uses the same session key from session_strategy)
  • During the buffer window, the webhook responds with 202 Accepted (no agent execution)
  • After the window expires, the concatenated message is processed and the response is delivered via callback
  • Compatible with split_config — buffered input can produce split output
  • The _buffered_messages count is added to webhookContext for telemetry
Combining Split + Buffer

Split and buffer solve complementary problems. Use both together for channels like WhatsApp:

{
"execution": {
"mode": "async",
"message_buffer": { "enabled": true, "window_ms": 5000 },
"split_config": { "enabled": true, "max_chunk_size": 1500 },
"callback": { "..." : "..." }
}
}

The flow becomes: multiple user messages → buffer → single agent execution → split response → multiple callback deliveries.


Webhook Config Reference

FieldOptionsDescription
auth.typesignature, api_key, noneValidation method
query_extraction.modefield, jmespath, template, full_bodyHow to extract the query
context_mappingobjectJSONPath mapping ($.field) from payload to agent context
response_adapter.formatebarn, slack, teams, twiml, rawResponse format
session_strategy.modederive, ephemeralderive = deterministic session, ephemeral = new each call
timeout_msnumberTimeout in ms (1000-300000, default: 30000)

Context Mapping

Maps fields from the webhook payload into the agent's execution context via JSONPath expressions. These fields are available to the LLM in the system prompt and to tools at runtime.

Naming Conventions

PatternVisibilityUse for
fieldNameVisible in LLM prompt + toolsData the LLM needs (IDs, names, locations)
_fieldNameTools only (hidden from prompt)Internal metadata (message SIDs, internal IDs)

Auto-excluded Fields

These fields are always excluded from the LLM prompt regardless of name — they remain accessible to tools via ${context.field}:

FieldReason
tokenAPI credential — used by tools for authentication
hostAPI base URL — used by tools for requests
userNameAlready rendered in the session context section

Examples

ERP Integration:

{
"context_mapping": {
"userId": "$.user.id",
"companyId": "$.company.id",
"companyName": "$.company.name",
"city": "$.company.address.city",
"state": "$.company.address.state",
"userName": "$.userName",
"token": "$.token",
"host": "$.host"
}
}

The LLM sees: userId, companyId, companyName, city, state. Excluded: userName (session section), token and host (credentials).

WhatsApp via Twilio:

{
"context_mapping": {
"_waId": "$.WaId",
"_messageSid": "$.MessageSid",
"phoneNumber": "$.From",
"profileName": "$.ProfileName"
}
}

The LLM sees: phoneNumber, profileName. Excluded: _waId and _messageSid (prefixed with _).

Safety Net: defaults_from_context

MCP Servers support defaults_from_context in their config to inject context values authoritatively into tool parameters — even if the LLM hallucinates a different value:

{
"config": {
"defaults_from_context": {
"body[].requesterId": "requesterId"
}
}
}

This maps webhookContext.requesterIdbody[].requesterId in every tool call, overriding whatever the LLM sends.


Sender Access Control

Control who can trigger a webhook before agent execution — zero LLM cost for unauthorized senders.

{
"sender_access": {
"mode": "members",
"identity_field": "From",
"match_column": "phone",
"resolve_user": true
}
}
ModeDescription
anyAny sender (default)
membersOnly active tenant members (verified against team directory)
whitelistOnly listed identifiers

Phone numbers are normalized before comparison: whatsapp:+5511999990000 becomes +5511999990000.


WhatsApp via Twilio

Twilio is a Business Solution Provider (BSP) for the WhatsApp Business API. The Zihin platform sends and receives WhatsApp messages through a Twilio number registered as a WhatsApp Sender. This guide covers the full integration end-to-end.

Direct Meta Cloud API

For lower cost and fewer moving parts, the Zihin platform also supports the official Meta WhatsApp Cloud API without going through a BSP. See Meta WhatsApp Cloud API setup below. Twilio is recommended when you already have Twilio infrastructure (SMS, voice) and want to centralize.

How the integration works

Lead sends WhatsApp message

Twilio receives + POSTs to your webhook

Zihin webhook endpoint (/api/triggers/webhook/:id)

Agent loop processes (LLM + tools + persona)

Zihin POSTs response back to Twilio Messages API

Twilio delivers to the lead's WhatsApp

Prerequisites

ItemWhereNotes
Twilio account, upgradedtwilio.comTrial accounts have severe restrictions
Twilio Number (BR or other)Phone Numbers > Buy a NumberLocal commercial number recommended
Brazil only: Regulatory Bundle approvedPhone Numbers > Regulatory ComplianceRequired for any BR number purchase
Meta Business Portfoliobusiness.facebook.comCan be created during Self Sign-up
WhatsApp Business Account (WABA)Created via Twilio Self Sign-upLinked to your Meta Business Portfolio
Display Name approved by MetaSet during WhatsApp Self Sign-upTakes hours to ~24h to approve

Step 1 — Buy a phone number on Twilio

In Twilio Console: Phone Numbers > Buy a Number.

For Brazilian numbers:

  • Choose Local or Mobile
  • Confirm the approved Regulatory Bundle is selected
  • ⚠️ Local Brazilian numbers have only Voice capability, not SMS — this is normal and does not affect WhatsApp routing

Step 2 — Start WhatsApp Sender Self Sign-up

In Twilio Console: Messaging > Senders > WhatsApp Senders > Create new sender.

  1. Select the phone number you just purchased
  2. Click Continue with Facebook — popup opens
  3. Inside the Facebook popup:
    • Log in with a Facebook account that is admin in your Meta Business Manager
    • Select or create the Meta Business Portfolio
    • Select Create a new WhatsApp Business Account (not "Use a display name only" — Twilio doesn't support that mode)
    • Set Display Name (e.g., "MyCompany Atendimento") — this is what leads see
    • Choose category and fill business profile
  4. On "Add your WhatsApp phone number":
    • ⚠️ Click "Add a new phone number" (NOT "Use only a display name")
    • Paste the number in international format: +551150287190

Step 3 — Verify the number

Important for Brazilian numbers

Brazilian local commercial numbers on Twilio have no SMS capability. The only working verification method is voice (phone call).

In the Meta popup, choose Phone call verification.

Meta will call the Twilio number to deliver a 6-digit OTP. You need to capture this OTP somehow — three options:

Option A: Forward the call to your cellphone (recommended)

Configure the Twilio number's Voice webhook to a TwiML that dials your cellphone:

  1. Twilio Console: Phone Numbers > Active Numbers > select your number
  2. Voice Configuration > "A call comes in":
    • Webhook URL: https://twimlets.com/forward?PhoneNumber=%2B55XXXXXXXXXXX&Timeout=60&CallerId=%2B<YOUR_TWILIO_NUMBER>
    • HTTP: HTTP POST
    • Replace +55XXXXXXXXXXX with your URL-encoded cellphone, and +<YOUR_TWILIO_NUMBER> with the Twilio number itself
    • Forcing CallerId to your Twilio number is critical — operators may filter the Meta POP number as spam (+551140402377 etc.)
  3. Click "Send code" in the Facebook popup
  4. Your cellphone rings showing +<YOUR_TWILIO_NUMBER> as caller
  5. Answer, listen to the 6-digit code, paste into the Facebook popup

Option B: Voicemail Twimlet (records and emails the transcription)

https://twimlets.com/voicemail?Email=you@example.com

Meta calls, the Twimlet records, transcribes (English), and emails. Less reliable for capturing exact digits (transcription sometimes truncates).

Option C: SIP softphone or Twilio Dev Phone

For ongoing operations. Overkill for one-time setup.

Step 4 — Complete the Self Sign-up

After verification succeeds, the popup closes. Twilio takes a few minutes to register the Sender. Status transitions: CREATINGONLINE (or PENDING_REVIEW while Meta approves the Display Name).

Step 5 — Configure the trigger in Zihin

Create a webhook trigger via POST /api/triggers with the following config (real example used in production):

{
"agent_id": "YOUR_AGENT_UUID",
"name": "WhatsApp Twilio Production",
"trigger_type": "webhook",
"trigger_config": {
"channel": {
"type": "twilio_whatsapp",
"auth": { "type": "basic", "secret_ref": "TWILIO_CREDENTIALS" }
},
"execution": {
"mode": "async",
"callback": {
"url": "https://api.twilio.com/2010-04-01/Accounts/<ACCOUNT_SID>/Messages.json",
"method": "POST",
"content_type": "application/x-www-form-urlencoded",
"auth": { "type": "basic", "secret_ref": "TWILIO_CREDENTIALS" },
"body_template": {
"To": "{{phoneNumber}}",
"Body": "{{agent_response}}",
"From": "whatsapp:+<YOUR_TWILIO_NUMBER>"
},
"response_transform": {
"max_length": 8000,
"strip_markdown": true
}
},
"ack_response": {
"body": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response></Response>",
"content_type": "text/xml"
},
"split_config": {
"enabled": true,
"numbering": false,
"max_chunks": 6,
"chunk_strategy": "paragraph",
"max_chunk_size": 1500,
"inter_chunk_delay_ms": 800
},
"message_buffer": {
"enabled": true,
"window_ms": 3000,
"max_messages": 8,
"concat_separator": "\n"
}
},
"sender_access": { "mode": "any" },
"context_mapping": {
"_waId": "$.WaId",
"_messageSid": "$.MessageSid",
"phoneNumber": "$.From",
"profileName": "$.ProfileName"
},
"query_extraction": { "mode": "field", "field": "Body" },
"response_adapter": { "format": "twiml" },
"session_strategy": { "mode": "derive", "fields": ["phoneNumber"] }
}
}

Field-by-field explanation:

FieldWhy this value
channel.type: "twilio_whatsapp"Tells Zihin to use the Twilio WhatsApp inbound adapter (extracts media, decodes Twilio-specific fields)
channel.auth.secret_refName of the secret containing <AccountSID>:<AuthToken> — store it via Tenant Secrets
execution.mode: "async"Required — Twilio's webhook timeout is ~15s, agent loops may take longer. Async returns ack immediately and delivers via callback
callback.urlTwilio Messages API. Replace <ACCOUNT_SID> with your real SID
body_template.FromYour Twilio WhatsApp number prefixed with whatsapp:. Must match the registered Sender
response_transform.max_lengthPer-chunk cap before the split_config runs. 8000 is safe — split breaks earlier at 1500
split_config (paragraph, 6×1500)Long agent responses get split by paragraph into up to 6 messages. WhatsApp respects natural breaks
message_buffer (3s window)Users often send 2-3 short messages in a row. Buffer concatenates them into a single agent execution
sender_access.mode: "any"Accept messages from any phone number (typical for inbound lead campaigns)
context_mappingMaps Twilio's payload fields (Body, From, ProfileName, WaId, MessageSid) into the agent's context
response_adapter.format: "twiml"Returns TwiML XML acknowledgment to Twilio's POST
session_strategy.derive(phoneNumber)Same lead = same session across messages

Step 6 — Wire the webhook in Twilio

After the trigger is created, Zihin returns the webhook_url:

https://llm.zihin.ai/api/triggers/webhook/<TRIGGER_ID>

In Twilio Console: Messaging > Senders > WhatsApp Senders > Edit Sender:

  • "When a message comes in":
    • URL: <webhook_url>
    • Method: HTTP POST
  • "Status callback URL" (optional, for delivery tracking): same URL or dedicated

Step 7 — Smoke test

Send a real WhatsApp message from your cellphone to the Twilio number. You should see in Zihin:

  • trigger_executions row with status=success
  • agent_executions row with iterations >= 1
  • The lead receives the agent's response within 3-10s

Brazil-specific gotchas

Phone number digit count

Brazilian commercial numbers on WhatsApp have 12 digits (+55<area><8-digit-number>, e.g., +551150287190). Brazilian mobile numbers have 13 digits with the leading 9 (+55<area>9<8-digit-number>, e.g., +5562999999999).

⚠️ Outbound to BR mobile: WhatsApp delivery is inconsistent with the 13-digit format. The 12-digit format (without the 9) is universally accepted. Twilio normalizes inbound From values, but To in outbound should match what was received.

Local numbers are Voice-only

When you buy a local BR number on Twilio, the page shows:

"Messaging configuration is unavailable for this phone number."

This is expected and does not affect WhatsApp Business. WhatsApp uses its own infrastructure layer separate from SMS.

Verification method must be Voice

Because there's no SMS capability, Meta's SMS-based verification fails silently. Always choose Phone call in the Self Sign-up popup, and have a forwarding rule ready (Step 3).

Meta rate limit on verification retries

Meta exponentially backs off: 60s1h3h24h between retries. Plan to get the verification right on the first 1-2 attempts.


Troubleshooting

SymptomLikely causeFix
Status OFFLINE with error 63111Sender's phone number or WABA mismatchedDelete the broken sender (DELETE /v2/Channels/Senders/<SID>), restart Self Sign-up
"Voice call" verification not receivedVoice webhook on the number points to demo URL (default)Set webhook to a forward Twimlet pointing to your cell (Step 3)
Cellphone doesn't ring during verificationOperator filtering Meta's POP number as spamForce CallerId parameter in the forward URL to your Twilio number
Verification SMS never arrivesBR number has no SMS capabilityUse Voice verification only
Outbound message error 63016"Outside the allowed window" (no inbound from lead in last 24h)Use an approved Template HSM, OR wait for lead to message first
Outbound returns 21617 ("body required")body_template missing Body fieldVerify trigger config has body_template.Body: "{{agent_response}}"
Sender stays PENDING_REVIEW for daysMeta is reviewing Display NameAllow up to 24h. If longer, contact Twilio support
Lead receives duplicated messagesOlder Zihin server version (pre-2026-05-11)Update to current — bug fixed
Agent narratives between tool calls don't reach leadOlder Zihin server version (pre-2026-05-11)Update to current — extractTurnResponse now preserves all turn messages

Validation queries (via Twilio API)

After setup, validate end-to-end via:

# 1. Sender state
curl -u $TWILIO_SID:$TWILIO_TOKEN \
"https://messaging.twilio.com/v2/Channels/Senders?Channel=whatsapp"

# 2. Last messages sent/received
curl -u $TWILIO_SID:$TWILIO_TOKEN \
"https://api.twilio.com/2010-04-01/Accounts/$TWILIO_SID/Messages.json?PageSize=10"

# 3. Trigger executions on Zihin side
curl -H "x-tenant-id: $TENANT_ID" -H "Authorization: Bearer $JWT" \
"https://llm.zihin.ai/api/triggers/$TRIGGER_ID/executions?limit=10"

Meta WhatsApp Cloud API (without Twilio)

If you prefer to remove the BSP layer:

  • Set channel.type: "meta_whatsapp" in trigger_config
  • Use Meta's Permanent Access Token instead of Twilio credentials ({ "type": "bearer", "secret_ref": "META_WHATSAPP_TOKEN" })
  • Set callback.url: "https://graph.facebook.com/v22.0/<PHONE_NUMBER_ID>/messages"
  • body_template uses JSON instead of form-urlencoded

The Zihin server handles both BSP (Twilio) and direct (Meta) channels — the only difference is in trigger_config. See Meta WhatsApp Cloud setup for obtaining the Permanent Access Token.

AspectTwilio (BSP)Meta Cloud API direct
Cost per messageMeta fee + Twilio markup (~$0.005/msg template)Only Meta fee
Monthly fee~$1/number$0
Setup complexity3 systems (Twilio + Meta + Zihin)2 systems (Meta + Zihin)
Webhook latency2 hops1 hop
Recommended forExisting Twilio investmentsMost new integrations

Execution Inspection

GET /api/triggers/:id/executions/:executionId

Returns enriched execution details with correlated session, LLM calls, and aggregated metrics.

Response:

{
"success": true,
"execution": {
"id": "uuid",
"trigger_id": "uuid",
"status": "success",
"started_at": "2026-02-18T14:30:00Z",
"finished_at": "2026-02-18T14:30:12Z",
"execution_time_ms": 12340,
"normalized_query": "List active contracts",
"agent_response": "Found 5 active contracts...",
"response_channel": "webhook_sync",
"tokens_used": 1250,
"session_id": "a1b2c3d4-...",
"webhook_context": {
"userId": "123",
"companyId": "456",
"company": "Acme Corp"
}
},
"session": {
"session_id": "a1b2c3d4-...",
"agent_id": "uuid",
"agent_name": "Sales Assistant",
"status": "active",
"created_at": "2026-02-18T14:00:00Z",
"updated_at": "2026-02-18T14:30:12Z",
"message_count": 8
},
"llm_calls": [
{
"model": "openai.gpt-4.1",
"provider": "openai",
"input_tokens": 850,
"output_tokens": 400,
"cached_input_tokens": 200,
"cost_usd": 0.0125,
"response_time_ms": 3200,
"tools_executed": ["search_contracts"],
"finish_reason": "stop",
"timestamp": "2026-02-18T14:30:02Z"
}
],
"tool_calls": [
{
"id": "uuid",
"tool_name": "search_contracts",
"tool_call_id": "call_abc123",
"tool_input": { "status": "active", "limit": 10 },
"tool_output": "{\"rows\": [...], \"count\": 5}",
"output_preview": "5 rows returned",
"success": true,
"duration_ms": 450,
"error_message": null,
"iteration": 1,
"call_index": 0,
"execution_id": "uuid",
"created_at": "2026-02-18T14:30:01Z"
}
],
"metrics": {
"total_calls": 2,
"total_tokens": 1250,
"total_cost_usd": 0.018,
"avg_latency_ms": 2800,
"models_used": ["openai.gpt-4.1"],
"providers_used": ["openai"]
}
}
tip

The tool_calls array contains granular details for each tool invocation. Older executions without tool call logs return an empty array.


Session → Trigger Navigation

GET /api/v1/sessions/:sessionId/trigger-context

Reverse navigation: from a session, discover which trigger and execution originated it. Returns has_trigger: false if the session was not created by a trigger.

Response (session from trigger):

{
"success": true,
"has_trigger": true,
"trigger": {
"id": "uuid",
"name": "ERP Chat Integration",
"trigger_type": "webhook",
"agent_id": "uuid",
"agent_name": "Sales Assistant",
"enabled": true
},
"execution": {
"id": "uuid",
"status": "success",
"started_at": "2026-02-18T14:30:00Z",
"finished_at": "2026-02-18T14:30:12Z",
"execution_time_ms": 12340
},
"navigation": {
"execution_detail": "/api/triggers/uuid/executions/uuid"
}
}

Error Codes

CodeHTTPDescription
invalid_input400Missing or invalid field
trigger_not_found404Trigger not found
trigger_disabled400Trigger is disabled
invalid_signature401HMAC/Svix validation failed
webhook_sender_denied403Webhook sender not authorized (sender_access)
execution_failed500Agent execution error