Skill: humanjesse/outbound-call (version 0.1.5)

--- name: outbound-call description: Make outbound phone calls via ElevenLabs voice agent and Twilio metadata: clawdbot: requires: env: - ELEVENLABS_API_KEY - ELEVENLABS_AGENT_ID - ELEVENLABS_PHONE_NUMBER_ID primaryEnv: ELEVENLABS_API_KEY --- # Outbound Call > **Source code and setup guide:** [github.com/humanjesse/hostinger-openclaw-guides](https://github.com/humanjesse/hostinger-openclaw-guides) Place outbound phone calls using the ElevenLabs voice agent with Twilio. The voice agent on the call uses OpenClaw as its brain — same as inbound calls. ## When to use When the user asks you to: - Call someone or phone someone - Make a phone call - Dial a number - Ring someone - Place a call to a number ## How to use Run the call script with a phone number in E.164 format: ```bash python3 skills/outbound-call/call.py +1XXXXXXXXXX ``` With an optional custom first message (what the agent says when the recipient picks up): ```bash python3 skills/outbound-call/call.py +1XXXXXXXXXX "Hi John, I'm calling about your appointment tomorrow." ``` With optional call context (passed as a dynamic variable to the agent): ```bash python3 skills/outbound-call/call.py +1XXXXXXXXXX "Hi, this is a quick follow-up call." "Customer requested callback about billing issue #4521" ``` ## Phone number format - US numbers: +1 followed by 10 digits, e.g., +15551234567 - If the user gives a number like 555-123-4567 or (555) 123-4567, reformat it to +15551234567 - Always confirm the formatted number with the user before placing the call ## Rules - ALWAYS confirm the phone number with the user before placing a call - NEVER place a call without explicit user consent - Report the result back to the user (conversation ID on success, error details on failure) - If the call fails, explain the error and suggest fixes

path: call.py
content: #!/usr/bin/env python3
"""Trigger an outbound phone call via ElevenLabs Conversational AI + Twilio."""
import sys
import os
import json
import re
import urllib.request
import urllib.error

AGENT_ID = os.environ.get("ELEVENLABS_AGENT_ID", "")
[REDACTED_SECRET]"ELEVENLABS_API_KEY", "")
PHONE_NUMBER_ID = os.environ.get("ELEVENLABS_PHONE_NUMBER_ID", "")

API_URL = "https://api.elevenlabs.io/v1/convai/twilio/outbound-call"


def make_call(to_number: str, first_message: str = "", context: str = "") -> dict:
    """Place an outbound call to the given phone number.

    Args:
        to_number: Phone number in E.164 format (+1XXXXXXXXXX for US).
        first_message: Optional custom greeting for this call.
        context: Optional context to pass as a dynamic variable.
    """
    if not [REDACTED_SECRET] {"error": "ELEVENLABS_API_KEY not set. Add it to your .env file."}
    if not AGENT_ID:
        return {"error": "ELEVENLABS_AGENT_ID not set. Add it to your .env file."}
    if not PHONE_NUMBER_ID:
        return {"error": "ELEVENLABS_PHONE_NUMBER_ID not set. Add it to your .env file."}

    # Validate E.164 format (US numbers: +1 followed by 10 digits)
    if not re.match(r'^\+1\d{10}$', to_number):
        return {"error": f"Invalid US phone number: {to_number}. Use +1XXXXXXXXXX format (E.164)."}

    payload = {
        "agent_id": AGENT_ID,
        "agent_phone_number_id": PHONE_NUMBER_ID,
        "to_number": to_number,
    }

    # Add optional overrides if provided
    client_data = {}
    if first_message:
        client_data["conversation_config_override"] = {
            "agent": {"first_message": first_message}
        }
    if context:
        client_data["dynamic_variables"] = {"call_context": context}
    if client_data:
        payload["conversation_initiation_client_data"] = client_data

    data = json.dumps(payload).encode()
    req = urllib.request.Request(API_URL, data=data, headers={
        "xi-api-key": API_KEY,
        "Content-Type": "application/json",
    })

    try:
        with urllib.request.urlopen(req) as resp:
            result = json.loads(resp.read())
            # ElevenLabs may return HTTP 200 with an error in the body
            if not result.get("success", True) or "error" in result:
                return {"error": result.get("error", result.get("message", "Unknown API error"))}
            return {
                "success": True,
                "conversation_id": result.get("conversation_id", ""),
                "call_sid": result.get("callSid", ""),
                "message": result.get("message", "Call initiated"),
            }
    except urllib.error.HTTPError as e:
        body = e.read().decode()
        return {"error": f"ElevenLabs API error (HTTP {e.code}): {body}"}
    except urllib.error.URLError as e:
        return {"error": f"Network error: {e.reason}"}


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(json.dumps({"error": "Usage: call.py <phone_number> [first_message] [context]"}))
        sys.exit(1)

    to = sys.argv[1]
    first_msg = sys.argv[2] if len(sys.argv) > 2 else ""
    ctx = sys.argv[3] if len(sys.argv) > 3 else ""

    result = make_call(to, first_msg, ctx)
    print(json.dumps(result, indent=2))
    sys.exit(0 if result.get("success") else 1)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)