| Duration | 2 hours |
| Day | 4 of 5 |
Learning Objectives
By the end of this module, students will be able to:
- Configure call recording settings
- Control recording programmatically
- Handle sensitive data appropriately
- Implement compliance requirements
Topics
1. Recording Basics (20 min)
Enabling Recording
from signalwire_agents import AgentBase
agent = AgentBase(name="recorded-agent")
# Enable recording
agent.set_params({
"record_call": True,
"record_format": "mp3"
})
Recording Formats
| Format | Quality | Size | Use Case |
|---|---|---|---|
mp3 | Good | Small | Archival |
wav | Best | Large | Analysis |
Stereo vs Mono
agent.set_params({
"record_call": True,
"record_stereo": True # Separate channels for caller/agent
})
Stereo: Left = caller, Right = agent (better for transcription) Mono: Mixed audio (smaller files)
2. Recording Controls (30 min)
Start Recording
@agent.tool(description="Start recording the call")
def start_recording(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult("I'm now recording this call for quality purposes.")
.record_call(control_id="main")
)
Stop Recording
@agent.tool(description="Stop recording")
def stop_recording(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult("Recording stopped.")
.stop_record_call(control_id="main")
)
Pause/Resume
@agent.tool(description="Pause recording for sensitive info")
def pause_recording(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult("Recording paused. Please provide your information.")
.stop_record_call(control_id="main")
)
@agent.tool(description="Resume recording")
def resume_recording(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult("Recording resumed.")
.record_call(control_id="main")
)
3. Sensitive Data Handling (25 min)
Auto-Pause Pattern
For general sensitive data (SSN, DOB, etc.), use the secure=True flag:
@agent.tool(
description="Collect sensitive verification info",
secure=True # Recording pauses automatically
)
def collect_sensitive_info(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
ssn_last_four = args.get("ssn_last_four", "")
# Process sensitive data
return SwaigFunctionResult(f"Verified SSN ending in {ssn_last_four}.")
Secure Payment Collection with pay()
For credit card collection, use the SWML pay method instead of SWAIG functions. This keeps card data away from the LLM entirely - card data is collected via IVR and sent directly to your payment gateway:
@agent.tool(
description="Process payment for customer",
parameters={
"type": "object",
"properties": {
"amount": {"type": "string", "description": "Amount to charge"}
},
"required": ["amount"]
}
)
def process_payment(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
amount = args.get("amount", "0.00")
# Get public URL from SDK (auto-detected from ngrok/proxy headers)
base_url = self.get_full_url().rstrip('/')
payment_url = f"{base_url}/payment"
return (
SwaigFunctionResult(
"I'll collect your payment securely. "
"Please enter your card using your phone keypad.",
post_process=True
)
.pay(
payment_connector_url=payment_url,
charge_amount=amount,
input_method="dtmf",
security_code=True,
postal_code=True
)
)
Benefits of the pay method:
- Card data never passes through the LLM
- Card data collected via IVR (DTMF or speech)
- Data sent directly to your payment gateway
- Only
pay_result(success/failure) returned to agent
Manual Pause for Sensitive Sections
@agent.tool(description="Collect social security number")
def collect_ssn(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult(
"For verification, I need your social security number. "
"Recording is paused for your privacy."
)
.stop_record_call(control_id="main")
)
@agent.tool(description="SSN received")
def ssn_received(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
ssn = args.get("ssn", "")
# Store securely, not in logs
last_four = ssn[-4:]
return (
SwaigFunctionResult(f"SSN ending in {last_four} verified. Resuming recording.")
.record_call(control_id="main")
.update_global_data( {"ssn_verified": True})
)
4. Compliance Patterns (25 min)
Recording Disclosure
Many jurisdictions require disclosure:
class CompliantAgent(AgentBase):
def __init__(self):
super().__init__(name="compliant-agent")
self.prompt_add_section(
"Recording Disclosure",
"At the start of every call, inform the caller: "
"'This call may be recorded for quality and training purposes.' "
"Wait for acknowledgment before proceeding."
)
# Start with recording off
self.set_params({
"record_call": False
})
@AgentBase.tool(description="Get consent and start recording")
def get_recording_consent(self, args: dict, raw_data: dict = None) -> SwaigFunctionResult:
consent = args.get("consent", "")
if consent.lower() in ["yes", "okay", "sure", "fine"]:
return (
SwaigFunctionResult("Thank you. How can I help you today?")
.record_call(control_id="main")
.update_global_data( {"recording_consent": True})
)
else:
return SwaigFunctionResult(
"No problem. This call will not be recorded. "
"How can I help you today?"
)
Recording with Beep
agent.set_params({
"record_call": True,
"record_beep": True # Periodic beep to indicate recording
})
Retention Notices
@agent.tool(description="Inform about recording retention")
def recording_info(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return SwaigFunctionResult(
"Call recordings are retained for 90 days for quality purposes, "
"then automatically deleted. You can request a copy or deletion "
"by emailing privacy@example.com."
)
5. Recording Webhooks (20 min)
Receiving Recording Status
Configure a webhook to receive recording events:
agent.set_params({
"record_call": True,
"record_webhook_url": "https://your-server.com/recording-status"
})
Your webhook receives:
{
"call_id": "abc123",
"recording_url": "https://signalwire.com/recordings/xyz.mp3",
"duration": 180,
"status": "completed"
}
Processing Recordings
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/recording-status")
async def handle_recording(request: Request):
data = await request.json()
if data["status"] == "completed":
# Store recording metadata
save_recording_metadata({
"call_id": data["call_id"],
"url": data["recording_url"],
"duration": data["duration"]
})
# Optionally trigger transcription
queue_transcription(data["recording_url"])
return {"status": "received"}
Complete Example
Click to reveal complete solution
#!/usr/bin/env python3
"""Compliant call recording agent."""
from signalwire_agents import AgentBase, SwaigFunctionResult
class RecordingAgent(AgentBase):
def __init__(self):
super().__init__(name="recording-agent")
self.prompt_add_section(
"Role",
"You help customers with account inquiries. "
"Always disclose recording at call start."
)
self.prompt_add_section(
"Recording Policy",
bullets=[
"Disclose recording at the start of the call",
"Pause recording when collecting sensitive data",
"Resume recording after sensitive data is processed",
"Never read back sensitive information"
]
)
self.add_language("English", "en-US", "rime.spore")
# Recording configuration
self.set_params({
"record_call": True,
"record_format": "mp3",
"record_stereo": True
})
self._setup_functions()
def _setup_functions(self):
@self.tool(description="Pause recording for sensitive info")
def secure_mode(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult(
"Recording paused. Please go ahead with your sensitive information."
)
.stop_record_call(control_id="main")
)
@self.tool(description="Resume normal recording")
def normal_mode(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
return (
SwaigFunctionResult("Thank you. Recording resumed.")
.record_call(control_id="main")
)
@self.tool(
description="Process payment for customer",
parameters={
"type": "object",
"properties": {
"amount": {"type": "string", "description": "Amount to charge"}
},
"required": ["amount"]
}
)
def process_payment(args: dict, raw_data: dict = None) -> SwaigFunctionResult:
amount = args.get("amount", "0.00")
# Get public URL from SDK (auto-detected from ngrok/proxy headers)
base_url = self.get_full_url().rstrip('/')
payment_url = f"{base_url}/payment"
# Card data collected via IVR, never touches the LLM
return (
SwaigFunctionResult(
"I'll collect your payment securely. "
"Please enter your card using your phone keypad.",
post_process=True
)
.pay(
payment_connector_url=payment_url,
charge_amount=amount,
input_method="dtmf",
security_code=True,
postal_code=True,
ai_response=(
"The payment result is ${pay_result}. "
"If successful, confirm the payment. "
"If failed, offer to try another card."
)
)
)
if __name__ == "__main__":
agent = RecordingAgent()
agent.run()
Key Takeaways
- Recording is configurable - Format, stereo, beep
- Control via actions - Start, stop, pause, resume
- Protect sensitive data - Pause for PCI/PII
- Compliance matters - Disclosure, consent, retention
- Webhooks for processing - Receive completed recordings
Preparation for Lab 2.7
- Review your organization’s recording policies
- Identify sensitive data your agent handles
- Plan pause/resume points
Lab Preview
In Lab 2.7, you will:
- Configure call recording
- Implement consent collection
- Add pause/resume for sensitive data
- Test recording controls