Skip to content

Hotel Call Automation - Webhook-Driven Call Chaining

Conversation: hotel-call-automation-plan Date: 2026-04-02 Client: Grand Estancia (Salem) - first implementation, planned for 20+ hotel clients


Overview

Automated call triggering system for hotel operations — check-in welcome calls, wake-up calls, and checkout reminders. Uses AstraPBX (self-hosted Asterisk) with webhook-driven sequential call dispatching. No external scheduling dependency on the VPS — all state and scheduling lives in Firebase/GCP.

Context

  • SIP Trunk: 10 channels total, Grand Estancia allocated 1 channel (soft limit, monitored not enforced)
  • Grand Estancia: 100 rooms, 60-70 bookings/day observed from logs
  • AstraPBX: Full control — AMI/ARI, webhook service, click-to-call API
  • Existing infra: Firebase Functions, Firestore, Cloud Scheduler, LogsUpdate service with /grandestancia-salem webhook

Architecture: Webhook-Driven Call Chaining

Why Not Cloud Tasks Concurrency?

We own the PBX. Instead of relying on Cloud Tasks max_concurrent_dispatches to serialize calls, we use Asterisk call-end events (via AstraPBX webhooks) to trigger the next call. This is event-driven, zero polling, and gives us real call completion confirmation.

Flow

Firestore Queue                AstraPBX (VPS)              Asterisk
─────────────          ──────────────────          ─────────────────

[call_1: pending]  ──→  POST /click-to-call  ──→  PJSIP originate
[call_2: pending]                                       |
[call_3: pending]                                   call ends
[call_4: pending]                                       |
     ...               Webhook fires  ←──────  ChannelDestroyed/Hangup
                            |
                       POST /call-chain-endpoint
                            |
                       Pick next pending  ──→  POST /click-to-call  ──→ ...
                       from Firestore

Separation of Concerns

GCP (stateful, persistent)               VPS (stateless, replaceable)
──────────────────────────               ──────────────────────────────
Cloud Scheduler (cron triggers)          Asterisk + AstraPBX Node.js
Firebase Functions (dispatch logic)  ←── Webhooks (call events)
Firestore (queue, state, metrics)    ──→ POST /api/v1/calls/click-to-call
  • VPS redeployment has zero impact on state — all queue/batch data is in Firestore
  • If VPS goes down mid-batch, a stale call checker (scheduled function every 5 min) detects calls stuck in "calling" status for >3 min and retries or skips

Firestore Data Model

Batch Document

/hotel_calls/{client_id}/batches/{batch_id}
{
  type: "wakeup" | "checkin_welcome" | "checkout_reminder",
  scheduled_at: timestamp,
  started_at: timestamp | null,
  completed_at: timestamp | null,
  status: "scheduled" | "in_progress" | "completed",
  total: 70,
  completed_count: 0,
  failed_count: 0,
  concurrency_limit: 1          // bump to 2-3 later without code change
}

Queue Document (per guest per call)

/hotel_calls/{client_id}/batches/{batch_id}/queue/{room_number}
{
  guest_name: "...",
  phone: "91XXXXXXXXXX",
  room: "204",
  status: "pending" | "calling" | "completed" | "failed" | "no_answer",
  call_id: null,
  attempt: 0,
  max_attempts: 2,
  started_at: null,
  ended_at: null,
  duration: null
}

Channel Metrics (for billing)

/channel_metrics/{client_id}/daily/{YYYY-MM-DD}
{
  total_calls: increment(1),
  total_seconds: increment(duration),
  peak_concurrent: max(current, active_count_at_call_start),
  hourly: {
    "07": { calls: increment(1), seconds: increment(duration) },
    ...
  }
}

Concurrency Control Logic

On call-end webhook:
  1. Mark call completed in Firestore
  2. Count currently "calling" docs in this batch
  3. If active < concurrency_limit AND pending docs exist:
       Pick next pending → fire click-to-call
  4. If no pending left AND no active calls:
       Mark batch "completed"
  • concurrency_limit: 1 → strictly sequential
  • Change to 3 later → 3 calls in parallel, each hangup triggers next, always stays at 3 active
  • This is a Firestore field change, no code change needed

Call Types & Triggers

Call Type Trigger When
Welcome call Firestore on_document_created on check-in doc Immediate or +30 min after check-in
Wake-up call Cloud Scheduler (@scheduler_fn.on_schedule) 7:00 AM daily
Checkout reminder Cloud Scheduler 9:00 AM daily

The scheduler/trigger doesn't fire calls directly — it creates a batch doc and fires the first N calls (N = concurrency_limit). The webhook chain handles the rest.


AstraPBX Webhook Setup

Register webhook for each hotel org:

POST /api/v1/webhooks
{
  org_id: "grandestancia",
  url: "https://your-logsupdate-service/astrapbx-call-chain",
  events: ["call.completed", "call.failed"],
  active: true
}

Webhook payload contains: call_id, duration, disposition, disconnected_by — enough to update queue doc and decide next action.


Failure Handling

On call-end webhook:
  if disposition == "no_answer" AND attempt < max_attempts:
    → set status: "pending", attempt: +1
    → push to end of queue (retry after all others)

  if disposition == "failed" (network/trunk error):
    → pause batch, alert operator
    → could be trunk down, don't burn through queue

Stale Call Checker

Scheduled function every 5 min: - Query all calls with status "calling" and started_at > 3 min ago - Mark as failed, trigger next call in queue - Handles: VPS crash, webhook delivery failure, network issues


Firestore Usage Estimate (20 clients, 100 guests/day, 3 call types)

Operation Daily Monthly
Writes ~32,000 ~960,000
Reads ~18,000 ~535,000
Deletes (cleanup >30d) ~6,000 ~182,000
Storage ~50 MB ~50 MB

Cost Breakdown

Free Tier Usage Billable Cost
Writes 20K/day 32K/day 12K/day ~$0.22/day
Reads 50K/day 18K/day 0 Free
Deletes 20K/day 6K/day 0 Free
Storage 1 GB 50 MB 0 Free

Monthly Firestore cost: ~$6-7

At 50 clients x 100 guests: ~$33/month. SIP minutes will always be the dominant cost.


Wake-up Call Timing Math

  • Average call duration: ~30 seconds (ring + IVR message + hangup)
  • 70 guests with 1 channel: ~35 minutes to complete all calls
  • Window: 7:00 AM - 7:35 AM (acceptable for wake-up)
  • With 2 channels: ~17 minutes
  • With 3 channels: ~12 minutes

Implementation Steps

Step What Where
1 Create call-chain webhook endpoint LogsUpdate server.py or new Firebase Function
2 Add batch creation logic for each call type Firebase Functions main.py
3 Add Cloud Scheduler triggers for wakeup + checkout Firebase Functions main.py
4 Add Firestore trigger for check-in → welcome call batch Firebase Functions main.py
5 Register webhooks in AstraPBX for each hotel org AstraPBX API
6 Add channel metrics tracking in call-chain endpoint Same as step 1
7 Add stale call checker (every 5 min) Firebase Functions main.py
8 Add billing dashboard / report generation Firebase Functions or separate

Key Files

File Role
FirebaseFunction/functions/main.py Scheduler triggers, batch creation, Firestore triggers
LogsUpdate/server.py Call-chain webhook endpoint, metrics logging
AstraPBX/src/services/webhookService.js Fires call-end events to your endpoint
AstraPBX/src/routes/calls.js POST /api/v1/calls/click-to-call — originates calls
AstraPBX/src/services/asterisk/callMonitoringService.js Real-time channel tracking