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.jsinsideapp.listencallback) - Queries
asterisk_cdr WHERE id > lastCdrId AND channel NOT LIKE 'Local/%' - Deduplicates by
linkedid— keeps one record per call (longest duration) - Extracts
org_idfrom channel name prefix (e.g.,PJSIP/org_mnd5khym_trunk...→ looks uporganizations.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_openedtemplate 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_idis 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¶
- CDR dedup — Poller groups by
linkedid, picks longest duration record - Ticket dedup — Auto-ticket checks for open ticket with same
caller_number - Bot dedup — 8-second delay checks if bot already created ticket for same
channel_id
Troubleshooting¶
Tickets not being created¶
- Check CDR poller:
pm2 logs astrapbx | grep "CDR poller" - Check auto-ticket logs:
gcloud run services logs read logsupdate --region us-central1 --limit 20 | grep AUTO-TICKET - 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
linkediddedup: records with same linkedid should produce one ticket - Check repeat caller dedup: same phone with open ticket should update, not create
WhatsApp not sending¶
- Check MSG91 authkey:
GET /api/v1/settings/msg91should showconfigured: true - Check ticket-whatsapp config:
GET /api/v1/settings/ticket-whatsappshould showenabled: true - Check Cloud Run logs for
[WA-TICKET]entries
Related Changes (Apr 8, 2026)¶
- 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