如何在IVR中保持会话?基于Twilio Webhook与PHP的技术求助
Hey there! Great progress on your IVR app so far—collecting Customer ID/PIN and sharing balances is a solid foundation. To let users skip re-authenticating for subsequent actions (PIN change, transfers) until the call ends, here's a practical approach using Twilio Webhooks and PHP, focused on session persistence tied to the Twilio Call SID:
Core Concepts to Make This Work
Since Twilio sends stateless HTTP requests for each IVR interaction, we need a way to track the user's authenticated state across those requests. Here's how to do it:
- Use the Call SID (Twilio sends this in every Webhook request as
CallSid) as a unique identifier for the call session. - Store authenticated user data (Customer ID, verified status) in a server-side store like Redis or your database—don't rely on PHP sessions, since Twilio's requests don't carry cookies.
- Build a simple state machine to track where the user is in the IVR flow (e.g., "authenticated, choosing action", "in PIN change flow") so you can route each request correctly.
Step-by-Step Implementation
First, make sure you have the Twilio PHP SDK installed via Composer:
composer require twilio/sdk
1. Initialize Session Storage & Twilio Client
We'll use Redis for fast, temporary session storage (replace with your database if needed):
<?php require_once 'vendor/autoload.php'; use Twilio\TwiML\VoiceResponse; // Get critical Twilio request parameters $callSid = $_POST['CallSid'] ?? ''; $userInput = $_POST['Digits'] ?? ''; // Connect to Redis (adjust host/port as needed) $redis = new Redis(); $redis->connect('localhost', 6379); $sessionKey = "ivr_call:$callSid"; // Fetch existing session data (if any) $sessionData = json_decode($redis->get($sessionKey), true) ?? []; $isAuthenticated = isset($sessionData['customer_id']) && $sessionData['is_verified'] === true; // Helper functions (replace with your actual business logic) function verifyCustomerPin(string $customerId, string $pin): bool { // Query your DB to check if PIN matches the customer ID return true; // Replace with real validation } function getCustomerBalance(string $customerId): string { // Fetch balance from your DB return "1234.56"; // Replace with real balance } function updateCustomerPin(string $customerId, string $newPin): bool { // Update PIN in your DB return true; // Replace with real update logic } ?>
2. Handle Unauthenticated Users (ID/PIN Collection)
If the user hasn't logged in yet, guide them through entering their credentials:
<?php if (!$isAuthenticated) { $response = new VoiceResponse(); // Step 1: Collect Customer ID if (empty($userInput)) { $gather = $response->gather([ 'numDigits' => 8, 'action' => 'ivr.php', 'method' => 'POST' ]); $gather->say('Please enter your customer ID, followed by the pound sign.'); echo $response; exit; } // Step 2: Collect PIN after getting Customer ID if (!isset($sessionData['customer_id'])) { $sessionData['customer_id'] = $userInput; $redis->set($sessionKey, json_encode($sessionData)); $gather = $response->gather([ 'numDigits' => 4, 'action' => 'ivr.php', 'method' => 'POST' ]); $gather->say('Please enter your PIN, followed by the pound sign.'); echo $response; exit; } // Step 3: Validate PIN and mark as authenticated $pinIsValid = verifyCustomerPin($sessionData['customer_id'], $userInput); if ($pinIsValid) { $sessionData['is_verified'] = true; $redis->set($sessionKey, json_encode($sessionData)); // Announce balance and present action menu $balance = getCustomerBalance($sessionData['customer_id']); $response->say("Your account balance is {$balance} dollars."); $gather = $response->gather([ 'numDigits' => 1, 'action' => 'ivr.php', 'method' => 'POST' ]); $gather->say('Please select an option: Press 1 to change your PIN, press 2 to make a transfer, press 3 to end the call.'); echo $response; exit; } else { // Invalid PIN, retry $gather = $response->gather([ 'numDigits' => 4, 'action' => 'ivr.php', 'method' => 'POST' ]); $gather->say('Invalid PIN. Please try again.'); echo $response; exit; } } ?>
3. Handle Authenticated Users (Action Menu & Flows)
Once the user is authenticated, let them navigate between actions without re-logging in:
<?php // User is authenticated: handle their menu choice $response = new VoiceResponse(); // Handle action selection if (!empty($userInput)) { switch ($userInput) { case '1': // Start PIN change flow $gather = $response->gather([ 'numDigits' => 4, 'action' => 'ivr.php?action=update_pin', 'method' => 'POST' ]); $gather->say('Please enter your new PIN, followed by the pound sign.'); echo $response; exit; case '2': // Start transfer flow (example first step: collect recipient ID) $gather = $response->gather([ 'numDigits' => 8, 'action' => 'ivr.php?action=transfer_recipient', 'method' => 'POST' ]); $gather->say('Please enter the recipient\'s customer ID, followed by the pound sign.'); echo $response; exit; case '3': // End call and clean up session $response->say('Thank you for using our service. Goodbye.'); $response->hangup(); $redis->del($sessionKey); echo $response; exit; default: // Invalid input, re-prompt menu $gather = $response->gather([ 'numDigits' => 1, 'action' => 'ivr.php', 'method' => 'POST' ]); $gather->say('Invalid selection. Please try again: Press 1 to change your PIN, press 2 to make a transfer, press 3 to end the call.'); echo $response; exit; } } // If no input yet, re-show the menu $gather = $response->gather([ 'numDigits' => 1, 'action' => 'ivr.php', 'method' => 'POST' ]); $gather->say('Please select an option: Press 1 to change your PIN, press 2 to make a transfer, press 3 to end the call.'); echo $response; ?>
4. Handle Specific Actions (e.g., PIN Update)
Add handlers for specific flows like PIN updates:
<?php // Handle PIN update completion if (isset($_GET['action']) && $_GET['action'] === 'update_pin') { $newPin = $userInput; $updateSuccess = updateCustomerPin($sessionData['customer_id'], $newPin); $response = new VoiceResponse(); if ($updateSuccess) { $response->say('Your PIN has been updated successfully.'); } else { $response->say('There was an error updating your PIN. Please try again later.'); } // Return to main menu $gather = $response->gather([ 'numDigits' => 1, 'action' => 'ivr.php', 'method' => 'POST' ]); $gather->say('Please select an option: Press 1 to change your PIN, press 2 to make a transfer, press 3 to end the call.'); echo $response; exit; } // Add more action handlers (like transfer steps) here ?>
Key Notes for Production
- Session Expiry: Set a TTL on your Redis keys (e.g.,
$redis->expire($sessionKey, 1800)for 30 minutes) to clean up stale sessions. - Error Handling: Add validation for invalid inputs (e.g., non-digit entries) and edge cases like empty Customer IDs.
- Security: Never store plaintext PINs—always hash them in your database before storing.
- Logging: Log Call SIDs and user actions for debugging and compliance.
内容的提问来源于stack exchange,提问作者HayWhy

