Skip to content

Auto-Ticket System (Zero Missed Calls)

Automatic ticket creation for missed calls, queue timeouts, and bot-dropped calls. Deployed April 8, 2026.

Architecture

graph TD
    A[Asterisk CDR] -->|cdr_adaptive_odbc| B[MySQL asterisk_cdr]
    B -->|every 30s poll| C[AstraPBX CDR Poller]
    C -->|POST inbound calls| D[events.astradial.com/auto-ticket]
    D -->|classify & create| E[Firestore tickets]
    D -->|WhatsApp notify| F[MSG91 API]
    D -->|trigger workflow| G[Gateway → Workflow Engine]
    E -->|real-time| H[Editor Tickets Page]

How It Works

CDR Poller (AstraPBX)

  • Runs every 30 seconds on VPS (server.js inside app.listen callback)
  • Queries asterisk_cdr WHERE id > lastCdrId AND channel NOT LIKE 'Local/%'
  • Deduplicates by linkedid — keeps one record per call (longest duration)
  • Extracts org_id from channel name prefix (e.g., PJSIP/org_mnd5khym_trunk... → looks up organizations.context_prefix)
  • POSTs inbound calls to events.astradial.com/auto-ticket/{org_id}

Auto-Ticket Classifier (LogsUpdate/bot-bridge)

  • Endpoint: POST /auto-ticket/{org_id}
  • Runs classification as a background task (non-blocking response)
Scenario Condition Source Priority
Missed call NO ANSWER, not queue missed_call high
Queue timeout NO ANSWER, queue in context queue_timeout high
Bot dropped ANSWERED, ext is per-org bot (see below), no ticket after 8s bot_dropped high
Human answered ANSWERED, non-bot ext Skipped
Repeat caller Open ticket exists for same phone Updated urgent
Outbound/internal direction ≠ inbound Skipped

WhatsApp Notification

  • After ticket creation, sends ticket_opened template via MSG91
  • Gets authkey from POST /api/v1/settings/msg91/key (internal endpoint)
  • Gets WhatsApp config from POST /api/v1/settings/ticket-whatsapp/internal

Workflow Trigger

  • If ticket_whatsapp.statuses.open.workflow_id is configured, triggers the workflow via gateway
  • Payload: { phone, source, ticket_id, timestamp, org_id }
  • Current workflow: 543322c2-2bc9-4649-b336-e04cf052c973

Bot Extensions

Per-org. Determined at classification time by querying AstraPBX:

POST /api/v1/users/internal/bot-extensions
Headers: X-Internal-Key
Body: { "org_id": "<uuid>" }
Response: { "extensions": ["1012", "1013", ...] }

Returns the org's user extensions where User.routing_type='ai_agent' AND status='active'. LogsUpdate caches the result per org for 5 minutes (_BOT_EXT_TTL_SECS); change a user's routing_type in the editor → up to 5 min before the classifier picks it up.

Why per-org: earlier the list was hardcoded as {1003, 1012, 1013} globally, which false-tagged human extensions reusing those numbers in unrelated orgs as bot_dropped. See Error 53 in troubleshooting for the full rewrite.

Failure mode: if the bot-extensions API call errors out, LogsUpdate falls back to an empty set — no extension is treated as a bot for that org during the failure window. This is "fail closed" — better to miss a real bot_dropped ticket for a few minutes than to false-tag human calls.

Key Files

File Location Purpose
CDR poller VPS /opt/astrapbx/src/server.js Poll MySQL, POST to auto-ticket
Auto-ticket endpoint Cloud Run server.py Classify CDR, create tickets
Firebase helpers Cloud Run firebase.py Dedup, repeat caller, create ticket
AMI CDR hook VPS asteriskManager.js Backup: POST on AMI CDR event
Tickets page Editor tickets/page.tsx UI + WhatsApp for in_progress/closed

Dedup Logic

  1. CDR dedup — Poller groups by linkedid, picks longest duration record
  2. Ticket dedup — Auto-ticket checks for open ticket with same caller_number
  3. Bot dedup — 8-second delay checks if bot already created ticket for same channel_id

Troubleshooting

Tickets not being created

  1. Check CDR poller: pm2 logs astrapbx | grep "CDR poller"
  2. Check auto-ticket logs: gcloud run services logs read logsupdate --region us-central1 --limit 20 | grep AUTO-TICKET
  3. Check if CDR has records: mysql -u root pbx_api_db -e "SELECT * FROM asterisk_cdr ORDER BY id DESC LIMIT 5"

Duplicate tickets

  • Check linkedid dedup: records with same linkedid should produce one ticket
  • Check repeat caller dedup: same phone with open ticket should update, not create

WhatsApp not sending

  1. Check MSG91 authkey: GET /api/v1/settings/msg91 should show configured: true
  2. Check ticket-whatsapp config: GET /api/v1/settings/ticket-whatsapp should show enabled: true
  3. Check Cloud Run logs for [WA-TICKET] entries
  • VPS timezone changed to Asia/Kolkata (IST)
  • Sequelize timezone set to +05:30
  • Call history deduped by linkedid (one record per call)
  • Live calls: CallerID shows DID number, To shows queue/extension
  • Recording playback: Content-Length header + Firebase Storage fallback via rclone
  • Audio player: shows from/to, duration, working progress bar
  • MSG91 WhatsApp: config endpoints fixed, numbers/templates/logs working