Skip to content

PipeCast Bridge — Webhook Call Transfer & Grand Estancia Bots

Status: Planning
Date: 2026-04-02
Feature Name: PipeCast Bridge (Pipecat + AstraPBX Call Transfer Bridge)

Problem

  1. Secrets management doesn't scale. Managing 20+ client orgs with hardcoded API keys in pipecat bots means redeployment for every credential change.
  2. Functions are dumb. flow_converter.py creates handlers that only transition nodes — no value mapping, no state, no logic. Any real behavior requires Python code, defeating the flow editor's purpose.
  3. Disconnected systems. pipecat-flow (SQLite) and AstraPBX (MySQL/Sequelize) have separate databases. Org credentials must be duplicated.

Goal: Build and deploy bots entirely from the flow editor — functions, webhooks, value mappings, secrets — with zero Python code per client.


Architecture Decision: Shared DB, Same VPS

Decision: Deploy pipecat-flow on the same VPS as AstraPBX (89.116.31.109). Migrate pipecat-flow from SQLite to MySQL, sharing AstraPBX's MySQL instance.

┌─────────────────────────────────────────────────────┐
│  Cloud VPS (89.116.31.109)                          │
│                                                     │
│  ┌──────────┐   ┌──────────────┐   ┌────────────┐  │
│  │ AstraPBX │   │ pipecat-flow │   │  Asterisk   │  │
│  │ :3000    │   │ :7860        │   │  AMI/ARI    │  │
│  └────┬─────┘   └──────┬───────┘   └─────────────┘  │
│       │                │                             │
│       └──────┬─────────┘                             │
│              ▼                                       │
│     ┌────────────────┐                               │
│     │  MySQL (shared) │                              │
│     │  pbx_multitenant│                              │
│     │                 │                              │
│     │  organizations  │ ← AstraPBX owns              │
│     │  api_keys       │                              │
│     │  ...            │                              │
│     │                 │                              │
│     │  pipecat_bots   │ ← pipecat-flow owns          │
│     │  pipecat_keys   │                              │
│     └────────────────┘                               │
│                                                      │
│  ┌─────────────────┐                                 │
│  │ flow-editor     │ ← Static site / Cloudflare      │
│  │ (reads orgs via │   Pages or same VPS              │
│  │  pipecat API)   │                                  │
│  └─────────────────┘                                 │
└──────────────────────────────────────────────────────┘

External:
┌──────────────┐
│  LogsUpdate  │  ← GCP Cloud Run (events.astradial.com)
│  /bot-bridge │    JWT proxy + ticket creation
└──────────────┘

Why same VPS + shared MySQL: - Single source of truth — pipecat reads organizations table directly for api_key, api_secret, context_prefix. No sync, no duplication. - Flow editor can list orgs — admin API returns org names/IDs from same DB. When creating a bot, you pick the org. Bot↔DID↔Queue assignment all in one place. - Simpler ops — one server to SSH into, one DB to backup, one deploy pipeline. - Secure — DB not exposed over network. Both apps connect via localhost:3306. - Performance is NOT a concern — pipecat-flow is a lightweight WebSocket relay + state machine. All heavy compute (VAD, STT, TTS, LLM) runs on Gemini Live cloud. The VPS just shuffles PCM audio bytes and manages flow state — minimal CPU/RAM overhead on top of existing Asterisk + AstraPBX. - Call transfers via localhost — pipecat calls AstraPBX at localhost:3000 directly. No network hop, no JWT token dance, no secrets management. Internal auth only.

What stays external: LogsUpdate on GCP Cloud Run (events.astradial.com) — used only for Firestore operations (ticket creation, CRM logging). Not needed for call transfers.


Solution Architecture

Call Transfer Flow (direct localhost — Option A)

Pipecat Bot (webhook post_action)
    ↓ POST localhost:3000/api/v1/calls/{channel_id}/transfer
    ↓ Internal auth (shared secret or localhost whitelist)
AstraPBX API (same VPS)
    ↓ AMI Redirect
Asterisk → routes call to queue/extension/DID

No LogsUpdate proxy, no JWT, no secrets in flow JSON. Just a localhost HTTP call.

Ticket Creation Flow (via LogsUpdate)

Pipecat Bot (webhook post_action)
    ↓ POST events.astradial.com/bot-bridge {action: "create_ticket", ...}
LogsUpdate (GCP Cloud Run)
    ↓ Firestore write
Ticket created in /astrapbx/{org_id}/tickets/

Component Design

1. Enhanced Function System (flow_converter.py + flow editor)

The core change: Functions defined in the flow editor should be able to do everything — not just transition nodes. Add two new capabilities to the FlowFunction schema:

a) state_mappings — Set state values when function is called

{
  "name": "select_department",
  "description": "Route caller to the right department",
  "properties": {
    "department": {
      "type": "string",
      "enum": ["front_desk", "housekeeping", "restaurant", "spa", "maintenance", "billing"]
    }
  },
  "state_mappings": {
    "selected_department": "{args.department}",
    "department_queue": "{value_map.department_queues.{args.department}}"
  },
  "next_node_id": "confirm_transfer"
}

state_mappings lets the editor define what gets saved to flow_manager.state when the LLM calls a function. Template variables: - {args.xxx} — LLM-provided argument value - {value_map.xxx.yyy} — Lookup from a value map (see below) - {call.channel_id} — From call_metadata

b) value_maps — Key-value lookups defined at flow level

Added to the flow JSON root (alongside meta, nodes, edges):

{
  "meta": { "name": "Grand Estancia IVR" },
  "value_maps": {
    "department_queues": {
      "front_desk": "5001",
      "housekeeping": "5002",
      "restaurant": "5003",
      "spa": "5004",
      "maintenance": "5005",
      "billing": "5006"
    }
  },
  "nodes": [...]
}

Why: This replaces hardcoded Python dicts. The IVR bot maps department → queue number entirely in JSON. The flow editor provides a UI to edit these maps. Different clients get different maps without code changes.

c) flow_converter.py handler upgrade

Current handler (dumb):

async def handler(args: FlowArgs):
    return args, build_node_config(target_node_id)

New handler (smart):

async def handler(args: FlowArgs, flow_manager):
    # 1. Always store raw args in state
    flow_manager.state.update(args)

    # 2. Apply state_mappings with template resolution
    for key, template in state_mappings.items():
        flow_manager.state[key] = resolve_template(
            template, args=args, 
            value_maps=value_maps,
            call=flow_manager.call_metadata
        )

    # 3. Transition to next node
    return args, build_node_config(target_node_id)

2. Webhook Action Type (actions.py)

New built-in action "webhook". Template variables in body resolved from flow_manager.call_metadata and flow_manager.state.

Transfer (direct localhost):

{
  "type": "webhook",
  "url": "http://localhost:3000/api/v1/calls/{call.channel_id}/transfer",
  "auth": "internal",
  "body": {
    "destination": "{state.department_queue}",
    "destination_type": "queue"
  }
}

Ticket creation (via LogsUpdate):

{
  "type": "webhook",
  "url": "https://events.astradial.com/bot-bridge",
  "body": {
    "action": "create_ticket",
    "org_id": "{call.org_id}",
    "caller_number": "{call.endpoint}",
    "category": "{state.category}",
    "summary": "{state.summary}",
    "guest_name": "{state.guest_name}",
    "room_number": "{state.room_number}"
  }
}

Auth modes: - "auth": "internal" — adds internal shared secret header (X-Internal-Key) for localhost AstraPBX calls. Key stored in env var, not in flow JSON. - No auth flag — plain POST (for LogsUpdate or external webhooks) - No API keys ever appear in flow JSON.

3. Channel ID Plumbing (pipeline.py)

AstraPBX already sends channel_id, org_id, endpoint via WebSocket customParameters (ariClient.js:311-316). Extract and attach to flow_manager.call_metadata.

4. Database Migration (pipecat-flow: SQLite → MySQL)

Migrate pipecat-flow gateway from SQLite (aiosqlite) to MySQL (aiomysql or databases). Tables:

  • pipecat_bots — bot definitions (flow JSON, model config)
  • pipecat_api_keys — gateway API keys for WebSocket auth

Reads from AstraPBX's existing tables: - organizations — org_id, api_key, api_secret, context_prefix, name

5. Bot Bridge Endpoint (LogsUpdate)

POST /bot-bridge with two actions: - pbx_forward — JWT caching + proxy to AstraPBX (refactored from /whatsapp-bridge) - create_ticket — Firestore ticket creation

6. Transfer Endpoint Enhancement (AstraPBX)

Add destination_type parameter to transfer endpoint. Auto-resolves context prefix: - "extension"${org.context_prefix}_internal - "queue"${org.context_prefix}_queue - "external"${org.context_prefix}_outbound

7. Flow Editor Enhancements

a) Webhook action type — New option in action dropdown with URL, body, auth fields.

b) State Mappings UI — In the function inspector, below properties/required, add a "State Mappings" section. Key-value pairs where values support template syntax.

c) Value Maps UI — Flow-level panel (alongside meta/settings) to define named key-value lookup tables.

d) Org Selector — When the editor is connected to the gateway API, show org dropdown for bot assignment. The auth: "org" webhook flag resolves to whichever org the bot is assigned to.

8. Grand Estancia Bots

Both defined entirely as JSON flows in the editor. No Python code.

IVR Bot — Department Router

Flow: welcome → select_department (value_map lookup) → confirm_transfer (webhook post_action) → end

Runtime example — caller says "I want to book a table for dinner":

1. Call arrives → AudioSocket → pipecat
   flow_manager.call_metadata = {channel_id: "1712345678.123", org_id: "org_abc"}
   flow_manager.state = {}

2. Welcome node loads → Gemini gets system prompt + function schema

3. Caller says: "I want to book a table for dinner"

4. Gemini calls: select_department(department: "restaurant")

5. Handler runs (flow_converter.py):
   ├─ flow_manager.state.update({"department": "restaurant"})       # raw args
   ├─ state_mappings resolve:
   │   "selected_dept" → "{args.department}"             → "restaurant"
   │   "queue_number"  → "{value_map.department_queues.{args.department}}" → "5003"
   └─ flow_manager.state = {department: "restaurant", selected_dept: "restaurant", queue_number: "5003"}
   → Transitions to confirm_transfer node

6. Gemini says: "I'll connect you to our restaurant right away."

7. Post-actions fire:
   ├─ webhook: POST http://localhost:3000/api/v1/calls/1712345678.123/transfer
   │   Header: X-Internal-Key: <from env>
   │   Body: {"destination": "5003", "destination_type": "queue"}
   │   → AstraPBX does AMI Redirect → Asterisk routes to queue 5003
   └─ end_conversation: call handed off, bot exits

Full flow JSON:

{
  "meta": { "name": "Grand Estancia IVR", "version": "0.1.0" },
  "value_maps": {
    "department_queues": {
      "restaurant": "5003",
      "housekeeping": "5002",
      "reception": "5001"
    }
  },
  "nodes": [
    {
      "id": "welcome",
      "type": "initial",
      "position": { "x": 0, "y": 0 },
      "data": {
        "label": "Welcome",
        "role_messages": [
          {
            "role": "system",
            "content": "You are the Grand Estancia hotel receptionist. Listen to what the caller needs and route them to the right department: restaurant, housekeeping, or reception. Be warm and professional."
          }
        ],
        "functions": [
          {
            "name": "select_department",
            "description": "Route the caller to the appropriate department",
            "properties": {
              "department": {
                "type": "string",
                "enum": ["restaurant", "housekeeping", "reception"],
                "description": "The department the caller wants"
              }
            },
            "required": ["department"],
            "state_mappings": {
              "selected_dept": "{args.department}",
              "queue_number": "{value_map.department_queues.{args.department}}"
            },
            "next_node_id": "confirm_transfer"
          }
        ]
      }
    },
    {
      "id": "confirm_transfer",
      "type": "end",
      "position": { "x": 0, "y": 300 },
      "data": {
        "label": "Transfer Call",
        "task_messages": [
          {
            "role": "system",
            "content": "Tell the caller you are connecting them to their requested department now. Be brief and polite."
          }
        ],
        "post_actions": [
          {
            "type": "webhook",
            "url": "http://localhost:3000/api/v1/calls/{call.channel_id}/transfer",
            "auth": "internal",
            "body": {
              "destination": "{state.queue_number}",
              "destination_type": "queue"
            }
          },
          {
            "type": "end_conversation"
          }
        ]
      }
    }
  ],
  "edges": [
    { "id": "e1", "source": "welcome", "target": "confirm_transfer", "label": "select_department" }
  ]
}

Adding a new client with different departments? Just create a new flow with a different value_map and enum. Zero Python code. Zero redeployment.

Ticketing Bot — Missed Call Handler

Flow: welcome → identify_issue → FAQ path (end) OR collect_details → submit_ticket → confirmation → end

Triggered when: Queue timeout (no agent picks up). Configured as queue failover destination in AstraPBX dialplan.


Repos & File Changes

Repo File Change
pipecat-flow /gateway/pipeline.py Extract call_metadata, attach to flow_manager
pipecat-flow /gateway/database.py SQLite → MySQL migration, read from organizations table
pipecat-flow /gateway/flow_converter.py Smart handlers: state_mappings, value_map resolution, template engine
pipecat-flow /src/pipecat_flows/actions.py New webhook action handler with template substitution
pipecat-flow /gateway/router_admin.py Org listing (reads from shared MySQL), bot CRUD
LogsUpdate /server.py /bot-bridge endpoint, extract shared forward_to_pbx() util
LogsUpdate /firebase.py create_ticket() method
AstraPBX /src/routes/calls.js destination_type on transfer endpoint
flow-editor lib/schema/flow.schema.ts state_mappings on FlowFunction, value_maps on FlowSchema, webhook Action fields
flow-editor components/inspector/forms/FunctionItem.tsx State mappings UI
flow-editor components/inspector/forms/ActionItem.tsx Webhook action fields
flow-editor New: value maps panel Flow-level value map editor

Implementation Order

  1. DB migration — pipecat-flow SQLite → MySQL (shared with AstraPBX)
  2. Channel ID plumbing — pipeline.py call_metadata extraction
  3. Template engine — shared resolve_template() for state_mappings and webhook bodies
  4. Enhanced flow_converter — state_mappings, value_map resolution in function handlers
  5. Webhook action type — actions.py with template substitution and auth: "org"
  6. Bot bridge endpoint — LogsUpdate /bot-bridge
  7. Transfer endpoint — AstraPBX destination_type support
  8. Flow editor schema — state_mappings, value_maps, webhook action fields
  9. Flow editor UI — function state mappings, value maps panel, webhook action form
  10. Grand Estancia IVR bot — JSON flow
  11. Grand Estancia Ticketing bot — JSON flow

Test Cases

TC-1xx: Database & Shared Infrastructure

ID Test Case Input Expected Result Status
TC-101 pipecat connects to MySQL Start pipecat-flow gateway Connects to pbx_multitenant MySQL on localhost:3306
TC-102 pipecat reads organizations table GET /admin/orgs Returns orgs from AstraPBX's organizations table
TC-103 pipecat bot CRUD in MySQL Create/read/update/delete bot pipecat_bots table operations work
TC-104 AstraPBX unaffected by pipecat tables Run AstraPBX normally No interference from pipecat_* tables
TC-105 Org credentials accessible Query org by ID api_key, api_secret readable (secret is bcrypt hash)

TC-2xx: Channel ID Plumbing

ID Test Case Input Expected Result Status
TC-201 AstraPBX call delivers channel_id AudioSocket with customParameters: {channel_id: "1712345678.123", org_id: "org_abc", provider: "astrapbx"} flow_manager.call_metadata has channel_id, org_id, endpoint
TC-202 Twilio call — no crash Incoming call via Twilio (no customParameters) flow_manager.call_metadata is {}, bot starts normally
TC-203 Missing channel_id AstraPBX call without channel_id in params call_metadata["channel_id"] is "", no crash
TC-204 State dict initialized Any call connects flow_manager.state is {} at start

TC-3xx: Template Engine

ID Test Case Input Expected Result Status
TC-301 {args.xxx} substitution Template: {args.department}, args: {department: "spa"} Result: "spa"
TC-302 {state.xxx} substitution Template: {state.queue}, state: {queue: "5001"} Result: "5001"
TC-303 {call.xxx} substitution Template: {call.channel_id}, metadata: {channel_id: "123.456"} Result: "123.456"
TC-304 {value_map.xxx.yyy} lookup Template: {value_map.depts.spa}, maps: {depts: {spa: "5004"}} Result: "5004"
TC-305 Dynamic value_map key Template: {value_map.depts.{args.department}}, args: {department: "spa"} Result: "5004"
TC-306 Nested dict substitution Body: {action: "x", body: {dest: "{state.queue}"}} Nested values substituted
TC-307 Missing variable — graceful Template: {state.nonexistent} Returns "" or None, no crash
TC-308 Missing value_map key Template: {value_map.depts.unknown} Returns "" or None, no crash

TC-4xx: Enhanced Function Handlers (flow_converter)

ID Test Case Input Expected Result Status
TC-401 Raw args stored in state LLM calls select_department(department: "spa") flow_manager.state includes {department: "spa"}
TC-402 state_mappings applied Mapping: {queue: "{value_map.depts.{args.department}}"} flow_manager.state.queue == "5004"
TC-403 State accumulates across nodes Node 1 sets {category: "maintenance"}, Node 2 sets {name: "John"} State has both
TC-404 State overwrites on conflict Node 1: {priority: "low"}, Node 2: {priority: "high"} state.priority == "high"
TC-405 Function without state_mappings Function with only next_node_id, no state_mappings Raw args saved, no error
TC-406 value_maps loaded from flow JSON Flow JSON has value_maps: {depts: {...}} Accessible in handler template resolution
TC-407 Decision routing still works Function with decision object Conditional routing unaffected by state_mappings

TC-5xx: Webhook Action Type

ID Test Case Input Expected Result Status
TC-501 Basic webhook fires {type: "webhook", url: "https://httpbin.org/post", body: {test: true}} HTTP POST sent, 200 logged
TC-502 Template substitution in body Body: {dest: "{state.queue}"} with state {queue: "5001"} Sent: {dest: "5001"}
TC-503 auth: "org" injects credentials Action has auth: "org", org has api_key: "org_xxx" api_key and api_secret injected into request body
TC-504 auth: "org" reads from MySQL Org ID from call_metadata → query organizations table Correct creds fetched from shared DB
TC-505 auth: "org" — org not found call_metadata has unknown org_id Error logged, webhook skipped, bot continues
TC-506 Webhook timeout (10s) URL never responds Timeout logged, bot continues
TC-507 Webhook HTTP error URL returns 500 Error logged with status, bot continues
TC-508 Webhook network error Unreachable hostname Connection error logged, bot continues
TC-509 Webhook as pre_action In pre_actions array Fires before node LLM response
TC-510 Webhook as post_action In post_actions array Fires after node transition
TC-511 Multiple webhooks in sequence Two webhooks in post_actions Both fire in order
TC-512 Ongoing actions count During action sequence _ongoing_actions_count correct

TC-6xx: Bot Bridge Endpoint (LogsUpdate)

ID Test Case Input Expected Result Status
TC-601 PBX forward — happy path POST /bot-bridge {action: "pbx_forward", api_key, api_secret, endpoint, body} JWT fetched, forwarded to AstraPBX, response returned
TC-602 JWT cached Two requests, same api_key, within 23h Only one login call to AstraPBX
TC-603 JWT expired (401 retry) Cached token expired Auto-refresh, retry, success
TC-604 Invalid credentials Wrong api_key/api_secret 401 returned, logged
TC-605 AstraPBX down Unreachable 502/503 returned, logged
TC-606 Create ticket — happy path {action: "create_ticket", org_id, caller_number, category, summary} Ticket in Firestore, ticket_id returned
TC-607 Create ticket — missing fields Missing category or summary 400 with validation error
TC-608 Create ticket — timestamp Any creation created_at in IST, status: "open"
TC-609 Unknown action {action: "unknown"} 400: "Unknown action"
TC-610 Shared util with whatsapp-bridge Same org on both endpoints Same JWT cache, same forward_to_pbx()

TC-7xx: Transfer Endpoint Enhancement (AstraPBX)

ID Test Case Input Expected Result Status
TC-701 destination_type=queue {destination: "5001", destination_type: "queue"} AMI Redirect to ${prefix}_queue
TC-702 destination_type=extension {destination: "101", destination_type: "extension"} AMI Redirect to ${prefix}_internal
TC-703 destination_type=external {destination: "9876543210", destination_type: "external"} AMI Redirect to ${prefix}_outbound
TC-704 No destination_type (backward compat) {destination: "101"} Existing behavior unchanged
TC-705 Invalid channel_id /calls/nonexistent/transfer 404
TC-706 Invalid destination_type {destination_type: "invalid"} 400

TC-8xx: Flow Editor — Schema & UI

ID Test Case Input Expected Result Status
TC-801 Webhook in action dropdown Open action type selector "Webhook" option visible
TC-802 Webhook fields shown Select webhook type URL, body textarea, auth checkbox appear
TC-803 Default URL pre-filled New webhook action https://events.astradial.com/bot-bridge
TC-804 state_mappings UI on function Open function inspector "State Mappings" section with key-value pairs
TC-805 state_mappings template autocomplete Type { in value field Suggests args., state., call., value_map.
TC-806 Value Maps panel Open flow settings Named value map editor (key-value table)
TC-807 Export includes all new fields Export JSON Contains value_maps, state_mappings, webhook actions
TC-808 Import flow with new fields Load JSON with value_maps + state_mappings Editor displays correctly
TC-809 Invalid JSON in webhook body Malformed JSON in body textarea Validation error shown
TC-810 Org selector for bot Create bot in editor Dropdown lists orgs from gateway API

TC-9xx: Grand Estancia IVR Bot

ID Test Case Input Expected Result Status
TC-901 Welcome message Call connects "Welcome to Grand Estancia..."
TC-902 Direct department request "I want housekeeping" select_department(department: "housekeeping")
TC-903 Indirect department detection "My AC isn't working" select_department(department: "maintenance")
TC-904 Ambiguous request "I need help" Bot asks clarifying question
TC-905 Value map resolves queue department=spa state.department_queue == "5004" (from value_map)
TC-906 Transfer webhook fires confirm_transfer node POST to /bot-bridge with correct queue
TC-907 All departments route Test all 6 departments Each maps to correct queue via value_map
TC-908 Bot ends after transfer Webhook sent end_conversation fires
TC-909 Flow JSON loads Register bot via admin API Bot loads, welcome node created

TC-10xx: Grand Estancia Ticketing Bot

ID Test Case Input Expected Result Status
TC-1001 Welcome on timeout Queue timeout → ticketing bot "Sorry we missed your call..."
TC-1002 FAQ — check-in time "What time is check-in?" Answers from FAQ, ends call
TC-1003 FAQ — WiFi "What's the WiFi?" Answers from FAQ
TC-1004 Non-FAQ → ticket path "My shower is leaking" is_faq: false, → collect_details
TC-1005 Detail collection "John, room 205" State: {guest_name: "John", room_number: "205"}
TC-1006 Ticket webhook fires submit_ticket → ticket_confirmed POST /bot-bridge with create_ticket + state
TC-1007 Ticket in Firestore After TC-1006 Document at /astrapbx/{org_id}/tickets/{id}
TC-1008 Confirmation message Ticket created "Your ticket has been created..."
TC-1009 Bot ends after ticket Confirmed end_conversation fires
TC-1010 Full state accumulation Entire flow State has category, summary, guest_name, room_number, details

TC-E2E: End-to-End Integration

ID Test Case Input Expected Result Status
TC-E01 Full IVR transfer PSTN → AstraPBX → AudioSocket → IVR bot → "front desk" → transfer Call lands on queue, agent picks up
TC-E02 Full ticketing PSTN → queue timeout → ticketing bot → describe issue → ticket Ticket in Firestore, call ends
TC-E03 No secrets leak Capture pipecat outbound traffic No api_key/secret in webhook body (only in LogsUpdate→AstraPBX)
TC-E04 Multi-org isolation Two orgs, same bot flow Each uses own creds, correct context routing
TC-E05 Webhook failure recovery AstraPBX down during transfer Bot says error, ends gracefully
TC-E06 Concurrent calls 3 simultaneous calls No cross-contamination of state/metadata
TC-E07 Editor → deploy → call Create flow in editor → export → register bot → make call Full round-trip works with no code

Total: 84 test cases


Bugs / Fixes Log

Track issues found during implementation here.

Context Notes

  • AstraPBX DB: MySQL (pbx_multitenant), Sequelize ORM, DB_HOST=localhost, DB_PORT=3306
  • Auth decision: Option A (direct localhost call). For call transfers, pipecat calls AstraPBX API directly on localhost:3000 — no JWT, no LogsUpdate proxy. Internal auth via shared secret or localhost whitelist. LogsUpdate /bot-bridge used only for Firestore operations (ticket creation).
  • channel_id comes from ARI channel.id, format like 1712345678.123
  • channel.name (endpoint) format like PJSIP/101-00000001
  • JWT tokens cached 23h in LogsUpdate (24h expiry from AstraPBX)
  • AudioSocket: raw 16-bit signed linear PCM at 8kHz
  • Flow converter passes pre_actions/post_actions through as-is from JSON
  • AstraPBX context prefix: org_{timestamp}_{type} (e.g., org_m1abc_internal)
  • Decision routing in editor uses action (Python code) + conditions — this still works alongside state_mappings