[object Object]

← back to Butlr

twilio: number-level voice-default + status-default fallbacks + deploy conf

ab050b2bb5b21a671aca3094d0ee42e62f601a03 · 2026-05-12 16:43:46 -0700 · SteveStudio2

Adds two Twilio webhook fallback routes used by the +16067130489
incoming-phone-number itself (not per-call):
  POST/GET /twilio/voice-default  — polite inbound rejection TwiML
  POST     /twilio/status-default — number-level status log-only

Wired via Twilio REST API on the number, so callbacks land at
butlr.agentabrams.com regardless of whether a worker has the right
PUBLIC_URL set. Belt-and-suspenders for the 2026-05-12 outage where
the local .env had a stale trycloudflare PUBLIC_URL.

Also adds .deploy.conf so /deploy can ship to Kamatera, and gitignores
runtime artifact dirs (data/recordings, transcripts, audio-cache,
menu-map) to prevent committed test debris.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit ab050b2bb5b21a671aca3094d0ee42e62f601a03
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 12 16:43:46 2026 -0700

    twilio: number-level voice-default + status-default fallbacks + deploy conf
    
    Adds two Twilio webhook fallback routes used by the +16067130489
    incoming-phone-number itself (not per-call):
      POST/GET /twilio/voice-default  — polite inbound rejection TwiML
      POST     /twilio/status-default — number-level status log-only
    
    Wired via Twilio REST API on the number, so callbacks land at
    butlr.agentabrams.com regardless of whether a worker has the right
    PUBLIC_URL set. Belt-and-suspenders for the 2026-05-12 outage where
    the local .env had a stale trycloudflare PUBLIC_URL.
    
    Also adds .deploy.conf so /deploy can ship to Kamatera, and gitignores
    runtime artifact dirs (data/recordings, transcripts, audio-cache,
    menu-map) to prevent committed test debris.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 .deploy.conf              |  6 ++++++
 .gitignore                |  5 +++++
 routes/twilio-webhooks.js | 29 +++++++++++++++++++++++++++++
 3 files changed, 40 insertions(+)

diff --git a/.deploy.conf b/.deploy.conf
new file mode 100644
index 0000000..e2df68a
--- /dev/null
+++ b/.deploy.conf
@@ -0,0 +1,6 @@
+PROJECT_NAME=butlr
+DEPLOY_HOST=45.61.58.125
+DEPLOY_PATH=/root/Projects/holdforme
+HEALTH_URL=https://butlr.agentabrams.com/healthz
+REQUIRED_ENVS="TWILIO_ACCOUNT_SID TWILIO_AUTH_TOKEN TWILIO_PHONE_NUMBER PUBLIC_URL HFM_ENC_KEY BUTLR_OWNER_TOKEN"
+INSTALL_CMD="npm ci --production"
diff --git a/.gitignore b/.gitignore
index 65a9d03..6434d09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,8 @@ build/
 .next/
 data/calls.json
 data/calls.json.lock
+data/recordings/
+data/transcripts/
+data/audio-cache/
+data/menu-map/
+
diff --git a/routes/twilio-webhooks.js b/routes/twilio-webhooks.js
index 0baf406..c67a187 100644
--- a/routes/twilio-webhooks.js
+++ b/routes/twilio-webhooks.js
@@ -328,6 +328,35 @@ router.post('/bridge/:call_id', express.urlencoded({ extended: true }), async (r
   }
 });
 
+// ── POST/GET /twilio/voice-default ────────────────────────────────────
+// Number-level inbound fallback. Anyone dialing the Butlr Twilio number
+// hears a polite recording — Butlr is outbound-only, the number is a
+// caller-ID, not a customer line.
+router.all('/voice-default', (req, res) => {
+  console.log('[twilio-webhook] inbound voice on Butlr number', {
+    From: req.body && req.body.From,
+    CallSid: req.body && req.body.CallSid,
+  });
+  res.type('text/xml').send(`<?xml version="1.0" encoding="UTF-8"?>
+<Response>
+  <Say voice="Polly.Joanna">Thank you for calling. This number is for outbound calls only. Please contact us through our website. Goodbye.</Say>
+  <Hangup/>
+</Response>`);
+});
+
+// ── POST /twilio/status-default ───────────────────────────────────────
+// Number-level status callback, log-only. Per-call StatusCallback is set
+// on the outbound call via lib/twilio.js and routes to /twilio/status/:id.
+router.post('/status-default', (req, res) => {
+  console.log('[twilio-webhook] number-level status', {
+    CallSid: req.body && req.body.CallSid,
+    CallStatus: req.body && req.body.CallStatus,
+    From: req.body && req.body.From,
+    To: req.body && req.body.To,
+  });
+  res.type('text/xml').send('<Response/>');
+});
+
 // ── GET /twilio/audio/announce/:hash.mp3 ──────────────────────────────
 // Serve the cached ElevenLabs MP3. Hash filename is opaque, no enumeration.
 router.get('/audio/announce/:hash', (req, res) => {

← 5150ae3 listen-bridge: per-call PCM archive + ffmpeg→MP3 finalize on  ·  back to Butlr  ·  deploy fingerprint route for verification fb73a00 →